Skip to content

Commit

Permalink
[Feat.] FunctionGraph metrics (#514)
Browse files Browse the repository at this point in the history
[Feat.] FunctionGraph metrics

Part of #438
implements: https://docs.otc.t-systems.com/function-graph/api-ref/apis/function_metrics/index.html

Reviewed-by: Artem Lifshits
Reviewed-by: Aloento
  • Loading branch information
anton-sidelnikov authored Feb 3, 2025
1 parent 53335a3 commit 9489610
Show file tree
Hide file tree
Showing 12 changed files with 378 additions and 0 deletions.
16 changes: 16 additions & 0 deletions doc/source/sdk/guides/function_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,19 @@ This API is used to query the alias of a function version.

.. literalinclude:: ../examples/function_graph/get_alias.py
:lines: 16-42

Querying Metrics in a Specified Period
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This API is used to query the alias of a function version.

.. literalinclude:: ../examples/function_graph/list_function_metrics.py
:lines: 16-35

Querying Tenant-Level Function Statistics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This API is used to query the alias of a function version.

.. literalinclude:: ../examples/function_graph/list_metrics.py
:lines: 16-24
8 changes: 8 additions & 0 deletions doc/source/sdk/proxies/function_graph.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,11 @@ Aliases
:noindex:
:members: aliases, create_alias, delete_alias,
update_alias, get_alias


Metrics
^^^^^^^

.. autoclass:: otcextensions.sdk.function_graph.v2._proxy.Proxy
:noindex:
:members: metrics, function_metrics
1 change: 1 addition & 0 deletions doc/source/sdk/resources/function_graph/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ FunctionGraph Resources
v2/event
v2/alias
v2/version
v2/metric
13 changes: 13 additions & 0 deletions doc/source/sdk/resources/function_graph/v2/metric.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
otcextensions.sdk.function_graph.v2.metric
==========================================

.. automodule:: otcextensions.sdk.function_graph.v2.metric

The Metric Class
----------------

The ``Metric`` class inherits from
:class:`~otcextensions.sdk.sdk_resource.Resource`.

.. autoclass:: otcextensions.sdk.function_graph.v2.metric.Metric
:members:
35 changes: 35 additions & 0 deletions examples/function_graph/list_function_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
# 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.
"""
Get all Function Metrics in a Specified Period
"""
import openstack
from otcextensions import sdk

openstack.enable_logging(True)
conn = openstack.connect(cloud='otc')
sdk.register_otc_extensions(conn)

func_attrs = {
'func_name': 'test-function',
'package': 'default',
'runtime': 'Python3.9',
'handler': 'index.handler',
'timeout': 30,
'memory_size': 128,
'code_type': 'inline',
}
fg = conn.functiongraph.create_function(**func_attrs)
for m in conn.functiongraph.function_metrics(
fg, period='1596679200000,1696679200000'):
print(m)
24 changes: 24 additions & 0 deletions examples/function_graph/list_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
# 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.
"""
Get all Tenant-Level Function metrics
"""
import openstack
from otcextensions import sdk

openstack.enable_logging(True)
conn = openstack.connect(cloud='otc')
sdk.register_otc_extensions(conn)

for m in conn.functiongraph.metrics(filter='monitor_data'):
print(m)
20 changes: 20 additions & 0 deletions otcextensions/sdk/function_graph/v2/_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from otcextensions.sdk.function_graph.v2 import event as _event
from otcextensions.sdk.function_graph.v2 import alias as _alias
from otcextensions.sdk.function_graph.v2 import version as _version
from otcextensions.sdk.function_graph.v2 import metric as _metric


class Proxy(proxy.Proxy):
Expand Down Expand Up @@ -440,3 +441,22 @@ def get_alias(self, function, alias):
_alias.Alias, alias,
function_urn=function.func_urn
)

# ======== Metric Methods ========

def metrics(self, **query):
"""List all tenant-level function statistics.
:returns: A generator of Metric instances.
"""
return self._list(_metric.Metric, **query)

def function_metrics(self, function, period):
"""List all metrics of a function in a specified period.
:returns: A generator of Metric instances.
"""
function = self._get_resource(_function.Function, function)
func_urn = function.func_urn.rpartition(":")[0]
base_path = f'/fgs/functions/{func_urn}/statistics/{period}'
return self._list(_metric.Metric, base_path=base_path)
153 changes: 153 additions & 0 deletions otcextensions/sdk/function_graph/v2/metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# 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 openstack import resource
from openstack import exceptions


class SlaReportsValue(resource.Resource):
timestamp = resource.Body('timestamp', type=int)
value = resource.Body('value', type=int)


class Metric(resource.Resource):
base_path = '/fgs/functions/statistics'
# Capabilities
allow_list = True

_query_mapping = resource.QueryParameters(
'filter', 'period', 'option',
'limit', 'marker'
)

# Attributes
count = resource.Body('count', type=list, list_type=SlaReportsValue)
duration = resource.Body('duration', type=list, list_type=SlaReportsValue)
fail_count = resource.Body(
'fail_count', type=list, list_type=SlaReportsValue
)
fail_rate = resource.Body(
'fail_rate', type=list, list_type=SlaReportsValue
)
max_duration = resource.Body(
'max_duration', type=list, list_type=SlaReportsValue
)
min_duration = resource.Body(
'min_duration', type=list, list_type=SlaReportsValue
)
reject_count = resource.Body(
'reject_count', type=list, list_type=SlaReportsValue
)
function_error_count = resource.Body(
'function_error_count', type=list, list_type=SlaReportsValue
)
system_error_count = resource.Body(
'system_error_count', type=list, list_type=SlaReportsValue
)
reserved_instance_num = resource.Body(
'reserved_instance_num', type=list, list_type=SlaReportsValue
)
concurrency_num = resource.Body(
'concurrency_num', type=list, list_type=SlaReportsValue
)

@classmethod
def list(
cls,
session,
paginated=True,
base_path=None,
allow_unknown_params=False,
*,
microversion=None,
**params,
):
"""This method is a generator which yields resource objects.
This resource object list generator handles pagination and takes query
params for response filtering.
:param session: The session to use for making this request.
:type session: :class:`~keystoneauth1.adapter.Adapter`
:param bool paginated: ``True`` if a GET to this resource returns
a paginated series of responses, or ``False`` if a GET returns only
one page of data. **When paginated is False only one page of data
will be returned regardless of the API's support of pagination.**
:param str base_path: Base part of the URI for listing resources, if
different from :data:`~openstack.resource.Resource.base_path`.
:param bool allow_unknown_params: ``True`` to accept, but discard
unknown query parameters. This allows getting list of 'filters' and
passing everything known to the server. ``False`` will result in
validation exception when unknown query parameters are passed.
:param str microversion: API version to override the negotiated one.
:param dict params: These keyword arguments are passed through the
:meth:`~openstack.resource.QueryParamter._transpose` method
to find if any of them match expected query parameters to be sent
in the *params* argument to
:meth:`~keystoneauth1.adapter.Adapter.get`. They are additionally
checked against the :data:`~openstack.resource.Resource.base_path`
format string to see if any path fragments need to be filled in by
the contents of this argument.
Parameters supported as filters by the server side are passed in
the API call, remaining parameters are applied as filters to the
retrieved results.
:return: A generator of :class:`Resource` objects.
:raises: :exc:`~openstack.exceptions.MethodNotSupported` if
:data:`Resource.allow_list` is not set to ``True``.
:raises: :exc:`~openstack.exceptions.InvalidResourceQuery` if query
contains invalid params.
"""
if not cls.allow_list:
raise exceptions.MethodNotSupported(cls, "list")
if 'filter' in params:
if params['filter'] == 'monitor_data':
cls.resources_key = 'monitor_data'
elif params['filter'] == 'monthly_report':
cls.resources_key = 'monthly_report'
session = cls._get_session(session)
microversion = cls._get_microversion(session, action='list')

if base_path is None:
base_path = cls.base_path
cls._query_mapping._validate(params, base_path=base_path)
query_params = cls._query_mapping._transpose(params, cls)
uri = base_path % params

while uri:
# Copy query_params due to weird mock unittest interactions
response = session.get(
uri,
headers={
'Accept': 'application/json',
'Content-Type': 'application/json'},
params=query_params.copy())
exceptions.raise_from_response(response)

data = response.json()

if cls.resources_key:
resources = data[cls.resources_key]
else:
resources = data

if not isinstance(resources, list):
resources = [resources]

for raw_resource in resources:
value = cls.existing(
microversion=microversion,
connection=session._get_connection(),
**raw_resource)
yield value

return
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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 datetime import datetime

from openstack import _log

from otcextensions.sdk.function_graph.v2 import function
from otcextensions.tests.functional.sdk.function_graph import TestFg

_logger = _log.setup_logging('openstack')


class TestFunctionQuotas(TestFg):
def test_list_metrics(self):
m = list(self.client.metrics(filter='monitor_data'))
self.assertIsNotNone(len(m[0].duration))

def test_list_func_metrics(self):
now = datetime.now().timestamp()
self.function = self.client.create_function(**TestFg.function_attrs)
assert isinstance(self.function, function.Function)
self.addCleanup(
self.client.delete_function,
self.function
)
m = list(self.client.function_metrics(
self.function, period=f'{now},{now - 3600}'))
self.assertGreaterEqual(1, len(m))
41 changes: 41 additions & 0 deletions otcextensions/tests/unit/sdk/function_graph/v2/test_metric.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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 openstack.tests.unit import base

from otcextensions.sdk.function_graph.v2 import metric


EXAMPLE = {
'duration': [
{
'timestamp': 1596679200000,
'value': -1
}
],
}


class TestFunctionInvocation(base.TestCase):

def test_basic(self):
sot = metric.Metric()
path = '/fgs/functions/statistics'
self.assertEqual(path, sot.base_path)
self.assertTrue(sot.allow_list)

def test_make_it(self):
sot = metric.Metric(**EXAMPLE)
self.assertEqual(EXAMPLE['duration'][0]['timestamp'],
sot.duration[0].timestamp)
self.assertEqual(EXAMPLE['duration'][0]['value'],
sot.duration[0].value)
Loading

0 comments on commit 9489610

Please sign in to comment.