Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC]: Metrics Refactoring #1492 Draft PR #1727

Closed
wants to merge 55 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
89d8baf
Created metrics cache class
Jul 5, 2022
6485b01
added unit tests, cleaned up naming
Jul 7, 2022
2a82a6b
moved unit testing files
Jul 7, 2022
1875ca5
fixing conditional logic in add_metric function, filled out name fiel…
Jul 7, 2022
1b0d91c
Changed dimensions to be list of Dimension objects instead of list of…
Jul 8, 2022
d1a0755
moved to pytest for unit testing
Jul 11, 2022
7d3ac3f
abstracted metric cache class and added pytest unit tests with cleare…
Jul 11, 2022
2fdacef
Adding rough metrics flush method
Jul 15, 2022
ac89405
Converting prints to loggers and emitting metrics to logs
Jul 18, 2022
460e22a
removed system metrics, creating custom handler
Jul 19, 2022
5758f43
reassigning the metric name and passing in yaml file as an argument
Jul 20, 2022
4d3c0d0
Adding log lines and setting up MetricsCache obj integration
Jul 25, 2022
419ba96
Adding more unit tests, fixing dimensions parsing
Jul 27, 2022
d7ffd48
added custom error class to act as wrapper for MetricsCache objects
Jul 28, 2022
2efd5e5
Added more unit tests for catching naming Metric strings
Jul 29, 2022
2bd8c66
Added more comments to code
Aug 1, 2022
01d3af9
working in torchserve start cmd
Aug 2, 2022
4abd315
getting rid of abs path method that is not in use
Aug 2, 2022
8340230
migrating additional add_metric methods from store to cache
Aug 5, 2022
b416bc7
Creating custom handler to test migrated methods and beginnings of te…
Aug 15, 2022
45d3fac
editing custom handler
Aug 15, 2022
6ced1ad
fixing custom handler
Aug 16, 2022
994af49
adding flags to reset Metrics after being emitted, trying custom handler
Aug 18, 2022
66e5162
revising custom handler and passing yaml file as arg
Aug 19, 2022
f299514
refactoring metrics log var name
Aug 19, 2022
5b69469
getting rid of unneeded log lines
Aug 19, 2022
1e585a2
editing default metric log path
Aug 19, 2022
a918be0
adding req ids to Metric objs parsed by yaml file
Aug 20, 2022
41cb423
editing custom handler
joshuaan7 Aug 22, 2022
e3a6fd3
Merge branch 'master' of https://github.com/joshuaan7/serve
joshuaan7 Aug 22, 2022
d11fb60
clearing log lines, commenting out unneeded sections
Aug 22, 2022
dd8542f
Merge branch 'master' of https://github.com/joshuaan7/serve
Aug 22, 2022
6331ce8
adding proper getter method for metrics log
joshuaan7 Aug 23, 2022
c95c422
Changes to get_metric functionality
Aug 24, 2022
da635c8
Merge branch 'master' of https://github.com/joshuaan7/serve
Aug 24, 2022
85ad4c8
typo
Aug 24, 2022
6314067
Merge branch 'master' into master
joshuaan7 Aug 24, 2022
a781ed7
linting
Aug 26, 2022
0c58bd8
Merge branch 'master' of https://github.com/joshuaan7/serve
Aug 26, 2022
407ab2e
Merge branch 'master' into master
joshuaan7 Aug 26, 2022
cc50528
Merge branch 'master' into master
maaquib Aug 31, 2022
6a27475
adding docs
Aug 31, 2022
bd2ae6e
Adding support for default yaml file config arg
Aug 31, 2022
48d8891
renaming metrics log to metrics config
Aug 31, 2022
2d55919
Merge branch 'master' into master
maaquib Sep 1, 2022
3aa4fb2
linting
Sep 1, 2022
6e35ae3
commenting out metrics_config in config properties
Sep 1, 2022
cfd8aaa
reformatting
Sep 1, 2022
58f0130
linting
Sep 1, 2022
16e96f1
Reformatting configsmanager file
Sep 2, 2022
d675ebe
fixing initial round of comments
joshuaan7 Sep 6, 2022
dc83f90
passing lint
joshuaan7 Sep 6, 2022
021d3f1
Merge branch 'master' into master
maaquib Sep 15, 2022
7b42779
Merge branch 'master' into master
maaquib Oct 4, 2022
13a6ce3
Merge branch 'master' into master
msaroufim Oct 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ts/metrics/metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Metric(object):
"""

def __init__(self, name, value,
unit, dimensions, request_id=None, metric_method=None):
unit, dimensions, request_id=None, metric_method=None, metric_type=None):
"""
Constructor for Metric class

Expand Down Expand Up @@ -50,6 +50,7 @@ def __init__(self, name, value,
self.value = value
self.dimensions = dimensions
self.request_id = request_id
self.metric_type = metric_type

def update(self, value):
"""
Expand Down
209 changes: 209 additions & 0 deletions ts/metrics/metric_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"""
1. Parse model_metrics portion of yaml file and get the data
2. Create Metric objects based off of the model_metrics yaml data
3. Create hash table with key being metricType_metricName_dimensions
and value being the respective Metric object created in the previous step

naming and unit testing
"""
import sys
import yaml
import psutil

from ts.metrics.metric import Metric
from ts.metrics.dimension import Dimension


class MetricsCache:
def __init__(self, yaml_file):
"""
Constructor for MetricsCaching class

MetricsCaching class will hold Metric objects in a cache that can be edited and read from.
The class also has the ability to parse a yaml file into Metrics objects

Parameters
----------
yaml_file: str
maaquib marked this conversation as resolved.
Show resolved Hide resolved
Name of yaml file to be parsed

"""
self.backend_cache = {} # hash table to store the Metric objects
self.yaml_file = yaml_file

def add_metric(self, metric_name: str, unit: str, dimensions: list, metric_type: str, value=0) -> None:
"""
Create a new metric and add into cache

Parameters
----------
metric_name: str
Name of metric
unit: str
unit can be one of ms, percent, count, MB, GB or a generic string
dimensions: list
list of dimension objects/strings read from yaml file
metric_type: str
Type of metric
value: int, float
value of metric

"""

if not isinstance(metric_name, str) or not isinstance(unit, str) or not isinstance(dimensions, list) or not \
isinstance(metric_type, str):
raise TypeError(f"metric_name must be a str, unit must be a str, "
f"dimensions must be a list of str, metric type must be a str")

# transforming Dimensions list into list of Dimension objects if not already
if isinstance(dimensions[0], Dimension): # this is ideal format
pass
# FIXME expecting even number of dimensions list - is this a correct assumption?
elif len(dimensions) % 2 == 0 and isinstance(dimensions[0], str):
temp_dimensions = []
for i in range(len(dimensions)):
if i % 2 == 0:
temp_dimensions.append(Dimension(name=dimensions[i], value=dimensions[i+1]))
dimensions = temp_dimensions
else:
raise ValueError(f"Dimensions list is expected to be an even number if the list of dimensions"
f" is made up of strings.")

print(f"Adding metric with fields of: metric name - {metric_name}, unit - {unit}, dimensions - {dimensions}, "
f"metric type - {metric_type}")

dims_str = "-".join([str(d) for d in dimensions])

self.backend_cache[f"{metric_type}-{metric_name}-{dims_str}"] = Metric(name=metric_name,
value=value,
unit=unit,
dimensions=dimensions,
metric_type=metric_type)
print("Successfully added metric.")

def get_metric(self, metric_key: str) -> Metric:
"""
Get a metric from cache

Parameters
----------
metric_key: str
Key to identify a Metric object within the cache

"""
if not isinstance(metric_key, str):
print(f"Only string types are acceptable as argument.")
sys.exit(1)

print(f"Getting metric {metric_key}")
metric_obj = self.backend_cache.get(metric_key)
if metric_obj:
print("Successfully received metric")
return metric_obj
else:
print("Metric does not exist.")
sys.exit(1)

def _parse_yaml_file(self) -> dict:
"""
Parse yaml file using PyYAML library.
"""
if not self.yaml_file:
print("No yaml file detected.")
sys.exit(1)
yml_dict = None
try:
stream = open(self.yaml_file, "r", encoding="utf-8")
yml_dict = yaml.safe_load(stream)
except yaml.YAMLError as exc:
print(f"Error parsing file: {exc}")
sys.exit(1)
except IOError as io_err:
print(f"Error reading file: {io_err}")
sys.exit(1)
except Exception as err:
print(f"General error: {err}")
sys.exit(1)

return yml_dict

def _parse_specific_metric(self, yaml_section="model_metrics") -> dict:
"""
Returns model_metrics portion of yaml file in a dict

Parameters
----------
yaml_section: str
section of yaml file to be parsed

"""
yaml_hash_table = self._parse_yaml_file()

print(f"Parsing {yaml_section} section of yaml file...")
try:
model_metrics_table = yaml_hash_table[yaml_section]
except KeyError as err:
print(f"'{yaml_section}' key not found in yaml file - {err}")
sys.exit(1)
print(f"Successfully parsed {yaml_section} section of yaml file")
return model_metrics_table

def _yaml_to_cache_util(self, model_metrics_table: dict) -> None:
"""
Create Metric objects based off of the model_metrics yaml data and add to hash table

Parameters
----------
model_metrics_table: dict
Parsed portion of the yaml file

"""
if not isinstance(model_metrics_table, dict):
print(f"model metrics is None and does not exist")
sys.exit(1)

print("Creating Metric objects")
for metric_type, metric_attributes_list in model_metrics_table.items():
metric_name = None
unit = None
dimensions = None
for metric_dict in metric_attributes_list:
try:
metric_name = metric_dict["name"]
unit = metric_dict["unit"]
dimensions = metric_dict["dimensions"]
except KeyError as err:
print(f"Key not found: {err}")
sys.exit(1)

self.add_metric(metric_name=metric_name, unit=unit, dimensions=dimensions, metric_type=metric_type)

print("Completed creating Metric objects")

def yaml_to_cache(self):
"""
Parses specific portion of yaml file and creates Metrics objects and adds to the cache
"""
model_metrics_table = self._parse_specific_metric()
self._yaml_to_cache_util(model_metrics_table=model_metrics_table)


if __name__ == "__main__":
# YAML to cache
backend_cache_obj = MetricsCache("../tests/metrics_yaml_testing/metrics.yaml")
backend_cache_obj.yaml_to_cache()

# Adding 1 host metric (CPUUtil) and 1 model metric (# of inferences),
# update the metric,
# and add to MetricsCache
dimension = [Dimension('Level', 'Host')]
# FIXME should i also use the existing Dimension class? probably yes
cpu_util_data = psutil.cpu_percent()
backend_cache_obj.add_metric(metric_name="CPUUtilization", value=cpu_util_data, unit="percent",
dimensions=dimension, metric_type="CPUUtilizationType")
print("================")
print(f"CPU UTIL METRIC")
cpu_util_metric = backend_cache_obj.get_metric("CPUUtilizationType-CPUUtilization-Level:Host")
print(cpu_util_metric)
cpu_util_metric.update(2.48)
print(cpu_util_metric)
Loading