Skip to content

Commit 733a5ba

Browse files
authored
Merge pull request #174 from Helene/analytics
Add analytics allowing inspect grafa-bridge performance
2 parents 67a66df + 9e75879 commit 733a5ba

File tree

7 files changed

+98
-15
lines changed

7 files changed

+98
-15
lines changed

source/analytics.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'''
2+
##############################################################################
3+
# Copyright 2024 IBM Corp.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
##############################################################################
17+
18+
Created on Feb 09, 2024
19+
20+
@author: HWASSMAN
21+
'''
22+
23+
global inspect
24+
inspect = False
25+
26+
global urllib3_debug
27+
urllib3_debug = 0

source/collector.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222

2323
import cherrypy
2424
import copy
25+
import analytics
2526
from queryHandler.Query import Query
2627
from messages import MSG
2728
from collections import defaultdict
2829
from typing import Optional, Any, List
2930
from threading import Thread
3031
from metadata import MetadataHandler
3132
from bridgeLogger import getBridgeLogger
32-
from utils import classattributes
33+
from utils import classattributes, cond_execution_time
3334

3435

3536
local_cache = set()
@@ -46,6 +47,7 @@ def __init__(self, columnInfo, dps, filtersMap):
4647

4748
self.parse_tags(filtersMap)
4849

50+
@cond_execution_time(enabled=analytics.inspect)
4951
def parse_tags(self, filtersMap):
5052
tagsDict = defaultdict(set)
5153
logger = getBridgeLogger()
@@ -107,6 +109,9 @@ def __init__(self, name: str, desc: str, type: Optional[str] = None):
107109
self.desc = desc
108110
self.timeseries: list[TimeSeries] = []
109111

112+
def __format__(self, spec):
113+
return f'{self.mname}_mTS'
114+
110115
def str_descfmt(self, original_counters=False) -> [str]:
111116
"""Format MetricTimeSeries description rows
112117
Output format:
@@ -267,6 +272,9 @@ def __init__(self, sensor: str, period: str, logger, request: QueryPolicy,
267272

268273
self.prepare_static_metrics_data()
269274

275+
def __format__(self, spec):
276+
return f'{self.sensor}_Collector'
277+
270278
@property
271279
def md(self):
272280
return MetadataHandler()
@@ -370,13 +378,14 @@ def _collect(self):
370378
self.metrics[columnInfo.keys[0].metric] = mt
371379
# self.logger.info(f'rows data {str(columnValues)}')
372380

381+
@cond_execution_time(enabled=analytics.inspect)
373382
def prepare_static_metrics_data(self):
374383
incl_metrics = list(self.request.metricsaggr.keys()
375384
) if self.request.metricsaggr else None
376385
self.setup_static_metrics_data(incl_metrics)
377386

387+
@cond_execution_time(enabled=analytics.inspect)
378388
def validate_query_filters(self):
379-
380389
# check filterBy settings
381390
if self.request.filters:
382391

@@ -424,6 +433,7 @@ def validate_query_filters(self):
424433
raise cherrypy.HTTPError(
425434
400, MSG['AttrNotValid'].format('filter'))
426435

436+
@cond_execution_time(enabled=analytics.inspect)
427437
def validate_group_tags(self):
428438
# check groupBy settings
429439
if self.request.grouptags:

source/opentsdb.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222

2323
import cherrypy
2424
import re
25+
import analytics
2526
from messages import ERR, MSG
2627
from collections import defaultdict
2728
from collector import SensorCollector, QueryPolicy
28-
from utils import getTimeMultiplier
29+
from utils import getTimeMultiplier, execution_time, cond_execution_time
2930
from typing import List
3031

3132

@@ -49,6 +50,7 @@ def qh(self):
4950
def TOPO(self):
5051
return self.__md.metaData
5152

53+
@cond_execution_time(enabled=analytics.inspect)
5254
def format_response(self, data: dict, jreq: dict) -> List[dict]:
5355
respList = []
5456
metrics = set(data.values())
@@ -63,6 +65,7 @@ def format_response(self, data: dict, jreq: dict) -> List[dict]:
6365
respList.append(res.to_dict(st.dps))
6466
return respList
6567

68+
@execution_time()
6669
def query(self, jreq: dict) -> List[dict]:
6770
resp = []
6871
collectors = []
@@ -107,6 +110,7 @@ def query(self, jreq: dict) -> List[dict]:
107110
cherrypy.response.headers['Access-Control-Allow-Origin'] = '*'
108111
return resp
109112

113+
@cond_execution_time(enabled=analytics.inspect)
110114
def build_collector(self, jreq: dict) -> SensorCollector:
111115

112116
q = jreq.get('inputQuery')

source/prometheus.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222

2323
import cherrypy
2424
import json
25+
import analytics
2526
from messages import MSG
2627
from typing import Optional
2728
from cherrypy.process.plugins import Monitor
2829
from collector import SensorCollector, QueryPolicy
29-
from utils import execution_time
30+
from utils import execution_time, cond_execution_time
3031

3132

3233
class PrometheusExporter(object):
@@ -56,6 +57,7 @@ def qh(self):
5657
def TOPO(self):
5758
return self.__md.metaData
5859

60+
@cond_execution_time(enabled=analytics.inspect)
5961
def format_response(self, data) -> [str]:
6062
resp = []
6163
for name, metric in data.items():
@@ -118,6 +120,7 @@ def initialize_cache_collectors(self):
118120
frequency=collector.period,
119121
name=thread_name).subscribe()
120122

123+
@cond_execution_time(enabled=analytics.inspect)
121124
def build_collector(self, sensor) -> SensorCollector:
122125

123126
period = self.md.getSensorPeriod(sensor)
@@ -136,8 +139,8 @@ def build_collector(self, sensor) -> SensorCollector:
136139
request = QueryPolicy(**attrs)
137140
collector = SensorCollector(sensor, period, self.logger, request)
138141

139-
self.logger.trace(f'request instance {str(request.__dict__)}')
140-
self.logger.trace(f'Created Collector instance {str(collector.__dict__)}')
142+
# self.logger.trace(f'request instance {str(request.__dict__)}')
143+
# self.logger.trace(f'Created Collector instance {str(collector.__dict__)}')
141144
return collector
142145

143146
def GET(self, **params):

source/queryHandler/PerfmonRESTclient.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
@author: HWASSMAN
2121
'''
2222

23+
import analytics
24+
from utils import cond_execution_time
2325
# catch import failure on AIX since we will not be shipping our third-party libraries on AIX
2426
try:
2527
import requests
@@ -28,6 +30,11 @@
2830
except Exception:
2931
pass
3032

33+
try: # for Python 3
34+
from http.client import HTTPConnection
35+
except ImportError:
36+
from httplib import HTTPConnection
37+
HTTPConnection.debuglevel = analytics.urllib3_debug
3138

3239
DEFAULT_HEADERS = {"Accept": "application/json",
3340
"Content-type": "application/json"}
@@ -78,6 +85,7 @@ def __init__(self, logger, reqdata=None, session=None):
7885
self.requestData = reqdata
7986
self.logger = logger
8087

88+
@cond_execution_time(enabled=analytics.inspect)
8189
def doRequest(self):
8290
if self.requestData and isinstance(self.requestData, requests.Request):
8391
_prepRequest = self.session.prepare_request(self.requestData)

source/queryHandler/QueryHandler.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
import operator
2525
import socket
2626
import time
27+
import analytics
2728
from collections import namedtuple, defaultdict
2829
from itertools import chain
2930
from typing import NamedTuple, Tuple
30-
from utils import execution_time
31+
from utils import cond_execution_time
3132

3233
from .PerfmonRESTclient import perfHTTPrequestHelper, createRequestDataObj, getAuthHandler
3334

@@ -433,7 +434,7 @@ def apiKeyData(self):
433434
def caCert(self):
434435
return self.__caCert
435436

436-
@execution_time()
437+
@cond_execution_time(enabled=analytics.inspect)
437438
def getTopology(self, ignoreMetrics=False):
438439
'''
439440
Returns complete topology as a single JSON string
@@ -454,7 +455,6 @@ def getTopology(self, ignoreMetrics=False):
454455
self.logger.error(
455456
'QueryHandler: getTopology response not valid json: {0} {1}'.format(res[:20], e))
456457

457-
@execution_time()
458458
def getAvailableMetrics(self):
459459
'''
460460
Returns output from topo -m
@@ -484,7 +484,7 @@ def deleteKeyFromTopology(self, keyStr, precheck=True):
484484
self.logger.error(
485485
'QueryHandler: deleteKeysFromTopology response not valid json: {0} {1}'.format(response[:20], e))
486486

487-
@execution_time()
487+
@cond_execution_time(enabled=analytics.inspect)
488488
def runQuery(self, query):
489489
'''
490490
runQuery: executes the given query based on the arguments.
@@ -509,7 +509,7 @@ def __do_RESTCall(self, endpoint, requestType='GET', params=None):
509509
Forward query request to the HTTPRequest client interface
510510
'''
511511

512-
self.logger.trace("__do_RESTcall invoke __ params: {} {} {}".format(endpoint, requestType, str(params)))
512+
# self.logger.trace("__do_RESTcall invoke __ params: {} {} {}".format(endpoint, requestType, str(params)))
513513

514514
try:
515515
_auth = getAuthHandler(*self.apiKeyData)
@@ -521,17 +521,17 @@ def __do_RESTCall(self, endpoint, requestType='GET', params=None):
521521
if _response.status_code == 200:
522522
return _response.content.decode('utf-8', "strict")
523523
elif _response.status_code == 401:
524-
self.logger.debug('Request headers:{}'.format(_response.request.headers))
525-
self.logger.debug('Request url:{}'.format(_response.request.url))
524+
self.logger.trace('Request headers:{}'.format(_response.request.headers))
525+
self.logger.trace('Request url:{}'.format(_response.request.url))
526526
msg = "Perfmon RESTcall error __ Server responded: {} {}".format(_response.status_code, _response.reason)
527527
self.logger.details(msg)
528528
raise PerfmonConnError("{} {}".format(_response.status_code, _response.reason))
529529
else:
530530
msg = "Perfmon RESTcall error __ Server responded: {} {}".format(_response.status_code, _response.reason)
531-
self.logger.debug(msg)
531+
self.logger.trace(msg)
532532
if _response.content:
533533
contentMsg = _response.content.decode('utf-8', "strict")
534-
self.logger.details(f'Response content:{contentMsg}')
534+
self.logger.trace(f'Response content:{contentMsg}')
535535
return None
536536
except TypeError as e:
537537
self.logger.exception(e)

source/utils.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,37 @@ def wrapper(*args: Any, **kwargs: Any) -> T:
5252
return outer
5353

5454

55+
def cond_execution_time(enabled: bool = False, skip_attribute: bool = False) -> Callable[[Callable[..., T]], Callable[..., T]]:
56+
""" Conditionally logs the name of the given function f with
57+
passed parameter values and the time it takes to execute it.
58+
"""
59+
def outer(f: Callable[..., T]) -> Callable[..., T]:
60+
@wraps(f)
61+
def wrapper(*args: Any, **kwargs: Any) -> T:
62+
self = args[0]
63+
if hasattr(self, 'logger'):
64+
logger = self.logger
65+
else:
66+
from bridgeLogger import getBridgeLogger
67+
logger = getBridgeLogger()
68+
args_str = ', '.join(map(str, args[1:])) if len(args) > 1 else ''
69+
kwargs_str = ', '.join(f'{k}={v}' for k, v in kwargs.items()) if len(kwargs) > 0 else ''
70+
logger.trace(MSG['StartMethod'].format(f.__name__, ', '.join(filter(None, [args_str, kwargs_str]))))
71+
t = time.time()
72+
result = f(*args, **kwargs)
73+
duration = time.time() - t
74+
if not skip_attribute:
75+
wrapper._execution_duration = duration # type: ignore
76+
logger.debug(MSG['RunMethod'].format(f.__name__, ', '.join(filter(None, [args_str, kwargs_str]))))
77+
logger.debug(MSG['TimerInfo'].format(f.__name__, duration))
78+
return result
79+
return wrapper
80+
81+
def no_outer(f: Callable[..., T]) -> Callable[..., T]:
82+
return f
83+
return outer if enabled else no_outer
84+
85+
5586
def classattributes(default_attr: dict, more_allowed_attr: list):
5687
""" class __init__decorator
5788
Parses kwargs attributes, for optional arguments uses default values,

0 commit comments

Comments
 (0)