diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 08cabf2de..a55453d65 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: d0bb12b34b0c487198c935001636b6163485a50f + CORE_REPO_SHA: 2d1f0b9f5fce62549d1338882f37b91b95881c75 jobs: build: diff --git a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py index aa28305ab..ea601ce8e 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_metrics_instrumentation.py @@ -15,97 +15,23 @@ from timeit import default_timer -from tornado.testing import AsyncHTTPTestCase - -from opentelemetry import trace from opentelemetry.instrumentation.tornado import TornadoInstrumentor -from opentelemetry.sdk.metrics.export import ( - HistogramDataPoint, - NumberDataPoint, +from opentelemetry.sdk.metrics.export import HistogramDataPoint + +from .test_instrumentation import ( # pylint: disable=no-name-in-module,import-error + TornadoTest, ) -from opentelemetry.test.test_base import TestBase - -from .tornado_test_app import make_app -class TornadoTest(AsyncHTTPTestCase, TestBase): - # pylint:disable=no-self-use - def get_app(self): - tracer = trace.get_tracer(__name__) - app = make_app(tracer) - return app - - def get_sorted_metrics(self): - resource_metrics = ( - self.memory_metrics_reader.get_metrics_data().resource_metrics - ) - for metrics in resource_metrics: - for scope_metrics in metrics.scope_metrics: - all_metrics = list(scope_metrics.metrics) - return self.sorted_metrics(all_metrics) - - @staticmethod - def sorted_metrics(metrics): - """ - Sorts metrics by metric name. - """ - return sorted( - metrics, - key=lambda m: m.name, - ) - - def assert_metric_expected( - self, metric, expected_value, expected_attributes - ): - data_point = next(iter(metric.data.data_points)) - - if isinstance(data_point, HistogramDataPoint): - self.assertEqual( - data_point.sum, - expected_value, - ) - elif isinstance(data_point, NumberDataPoint): - self.assertEqual( - data_point.value, - expected_value, +class TestTornadoMetricsInstrumentation(TornadoTest): + # Return Sequence with one histogram + def create_histogram_data_points(self, sum_data_point, attributes): + return [ + self.create_histogram_data_point( + sum_data_point, 1, sum_data_point, sum_data_point, attributes ) + ] - self.assertDictEqual( - expected_attributes, - dict(data_point.attributes), - ) - - def assert_duration_metric_expected( - self, metric, duration_estimated, expected_attributes - ): - data_point = next(iter(metric.data.data_points)) - - self.assertAlmostEqual( - data_point.sum, - duration_estimated, - delta=200, - ) - - self.assertDictEqual( - expected_attributes, - dict(data_point.attributes), - ) - - def setUp(self): - super().setUp() - TornadoInstrumentor().instrument( - server_request_hook=getattr(self, "server_request_hook", None), - client_request_hook=getattr(self, "client_request_hook", None), - client_response_hook=getattr(self, "client_response_hook", None), - meter_provider=self.meter_provider, - ) - - def tearDown(self): - TornadoInstrumentor().uninstrument() - super().tearDown() - - -class TestTornadoInstrumentor(TornadoTest): def test_basic_metrics(self): start_time = default_timer() response = self.fetch("/") @@ -132,42 +58,51 @@ class TestTornadoInstrumentor(TornadoTest): ) self.assert_metric_expected( server_active_request, - 0, - { - "http.method": "GET", - "http.flavor": "HTTP/1.1", - "http.scheme": "http", - "http.target": "/", - "http.host": response.request.headers["host"], - }, + [ + self.create_number_data_point( + 0, + attributes={ + "http.method": "GET", + "http.flavor": "HTTP/1.1", + "http.scheme": "http", + "http.target": "/", + "http.host": response.request.headers["host"], + }, + ), + ], ) self.assertEqual(server_duration.name, "http.server.duration") - self.assert_duration_metric_expected( + self.assert_metric_expected( server_duration, - client_duration_estimated, - { - "http.status_code": response.code, - "http.method": "GET", - "http.flavor": "HTTP/1.1", - "http.scheme": "http", - "http.target": "/", - "http.host": response.request.headers["host"], - }, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.method": "GET", + "http.flavor": "HTTP/1.1", + "http.scheme": "http", + "http.target": "/", + "http.host": response.request.headers["host"], + "http.status_code": response.code, + }, + ), + est_value_delta=200, ) self.assertEqual(server_request_size.name, "http.server.request.size") self.assert_metric_expected( server_request_size, - 0, - { - "http.status_code": 200, - "http.method": "GET", - "http.flavor": "HTTP/1.1", - "http.scheme": "http", - "http.target": "/", - "http.host": response.request.headers["host"], - }, + self.create_histogram_data_points( + 0, + attributes={ + "http.status_code": 200, + "http.method": "GET", + "http.flavor": "HTTP/1.1", + "http.scheme": "http", + "http.target": "/", + "http.host": response.request.headers["host"], + }, + ), ) self.assertEqual( @@ -175,37 +110,44 @@ class TestTornadoInstrumentor(TornadoTest): ) self.assert_metric_expected( server_response_size, - len(response.body), - { - "http.status_code": response.code, - "http.method": "GET", - "http.flavor": "HTTP/1.1", - "http.scheme": "http", - "http.target": "/", - "http.host": response.request.headers["host"], - }, + self.create_histogram_data_points( + len(response.body), + attributes={ + "http.status_code": response.code, + "http.method": "GET", + "http.flavor": "HTTP/1.1", + "http.scheme": "http", + "http.target": "/", + "http.host": response.request.headers["host"], + }, + ), ) self.assertEqual(client_duration.name, "http.client.duration") - self.assert_duration_metric_expected( + self.assert_metric_expected( client_duration, - client_duration_estimated, - { - "http.status_code": response.code, - "http.method": "GET", - "http.url": response.effective_url, - }, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.status_code": response.code, + "http.method": "GET", + "http.url": response.effective_url, + }, + ), + est_value_delta=200, ) self.assertEqual(client_request_size.name, "http.client.request.size") self.assert_metric_expected( client_request_size, - 0, - { - "http.status_code": response.code, - "http.method": "GET", - "http.url": response.effective_url, - }, + self.create_histogram_data_points( + 0, + attributes={ + "http.status_code": response.code, + "http.method": "GET", + "http.url": response.effective_url, + }, + ), ) self.assertEqual( @@ -213,12 +155,14 @@ class TestTornadoInstrumentor(TornadoTest): ) self.assert_metric_expected( client_response_size, - len(response.body), - { - "http.status_code": response.code, - "http.method": "GET", - "http.url": response.effective_url, - }, + self.create_histogram_data_points( + len(response.body), + attributes={ + "http.status_code": response.code, + "http.method": "GET", + "http.url": response.effective_url, + }, + ), ) def test_metric_uninstrument(self): @@ -226,10 +170,10 @@ class TestTornadoInstrumentor(TornadoTest): TornadoInstrumentor().uninstrument() self.fetch("/") - metrics_list = self.memory_metrics_reader.get_metrics_data() - for resource_metric in metrics_list.resource_metrics: - for scope_metric in resource_metric.scope_metrics: - for metric in scope_metric.metrics: - for point in list(metric.data.data_points): - if isinstance(point, HistogramDataPoint): - self.assertEqual(point.count, 1) + metrics = self.get_sorted_metrics() + self.assertEqual(len(metrics), 7) + + for metric in metrics: + for point in list(metric.data.data_points): + if isinstance(point, HistogramDataPoint): + self.assertEqual(point.count, 1) diff --git a/instrumentation/opentelemetry-instrumentation-urllib/tests/test_metrics_instrumentation.py b/instrumentation/opentelemetry-instrumentation-urllib/tests/test_metrics_instrumentation.py index a87e3f97b..c9417fc67 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib/tests/test_metrics_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-urllib/tests/test_metrics_instrumentation.py @@ -14,7 +14,6 @@ from timeit import default_timer -from typing import Optional, Union from urllib import request from urllib.parse import urlencode @@ -23,16 +22,11 @@ import httpretty from opentelemetry.instrumentation.urllib import ( # pylint: disable=no-name-in-module,import-error URLLibInstrumentor, ) -from opentelemetry.sdk.metrics._internal.point import Metric -from opentelemetry.sdk.metrics.export import ( - HistogramDataPoint, - NumberDataPoint, -) from opentelemetry.semconv.metrics import MetricInstruments from opentelemetry.test.test_base import TestBase -class TestRequestsIntegration(TestBase): +class TestUrllibMetricsInstrumentation(TestBase): URL = "http://httpbin.org/status/200" URL_POST = "http://httpbin.org/post" @@ -50,63 +44,13 @@ class TestRequestsIntegration(TestBase): URLLibInstrumentor().uninstrument() httpretty.disable() - def get_sorted_metrics(self): - resource_metrics = ( - self.memory_metrics_reader.get_metrics_data().resource_metrics - ) - - all_metrics = [] - for metrics in resource_metrics: - for scope_metrics in metrics.scope_metrics: - all_metrics.extend(scope_metrics.metrics) - - return self.sorted_metrics(all_metrics) - - @staticmethod - def sorted_metrics(metrics): - """ - Sorts metrics by metric name. - """ - return sorted( - metrics, - key=lambda m: m.name, - ) - - def assert_metric_expected( - self, - metric: Metric, - expected_value: Union[int, float], - expected_attributes: dict, - est_delta: Optional[float] = None, - ): - data_point = next(iter(metric.data.data_points)) - - if isinstance(data_point, HistogramDataPoint): - self.assertEqual( - data_point.count, - 1, + # Return Sequence with one histogram + def create_histogram_data_points(self, sum_data_point, attributes): + return [ + self.create_histogram_data_point( + sum_data_point, 1, sum_data_point, sum_data_point, attributes ) - if est_delta is None: - self.assertEqual( - data_point.sum, - expected_value, - ) - else: - self.assertAlmostEqual( - data_point.sum, - expected_value, - delta=est_delta, - ) - elif isinstance(data_point, NumberDataPoint): - self.assertEqual( - data_point.value, - expected_value, - ) - - self.assertDictEqual( - expected_attributes, - dict(data_point.attributes), - ) + ] def test_basic_metric(self): start_time = default_timer() @@ -127,31 +71,33 @@ class TestRequestsIntegration(TestBase): ) self.assert_metric_expected( client_duration, - client_duration_estimated, - { - "http.status_code": str(result.code), - "http.method": "GET", - "http.url": str(result.url), - "http.flavor": "1.1", - }, - est_delta=200, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.status_code": str(result.code), + "http.method": "GET", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), + est_value_delta=200, ) - # net.peer.name - self.assertEqual( client_request_size.name, MetricInstruments.HTTP_CLIENT_REQUEST_SIZE, ) self.assert_metric_expected( client_request_size, - 0, - { - "http.status_code": str(result.code), - "http.method": "GET", - "http.url": str(result.url), - "http.flavor": "1.1", - }, + self.create_histogram_data_points( + 0, + attributes={ + "http.status_code": str(result.code), + "http.method": "GET", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), ) self.assertEqual( @@ -160,13 +106,15 @@ class TestRequestsIntegration(TestBase): ) self.assert_metric_expected( client_response_size, - result.length, - { - "http.status_code": str(result.code), - "http.method": "GET", - "http.url": str(result.url), - "http.flavor": "1.1", - }, + self.create_histogram_data_points( + result.length, + attributes={ + "http.status_code": str(result.code), + "http.method": "GET", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), ) def test_basic_metric_request_not_empty(self): @@ -191,14 +139,16 @@ class TestRequestsIntegration(TestBase): ) self.assert_metric_expected( client_duration, - client_duration_estimated, - { - "http.status_code": str(result.code), - "http.method": "POST", - "http.url": str(result.url), - "http.flavor": "1.1", - }, - est_delta=200, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.status_code": str(result.code), + "http.method": "POST", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), + est_value_delta=200, ) self.assertEqual( @@ -207,13 +157,15 @@ class TestRequestsIntegration(TestBase): ) self.assert_metric_expected( client_request_size, - len(data_encoded), - { - "http.status_code": str(result.code), - "http.method": "POST", - "http.url": str(result.url), - "http.flavor": "1.1", - }, + self.create_histogram_data_points( + len(data_encoded), + attributes={ + "http.status_code": str(result.code), + "http.method": "POST", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), ) self.assertEqual( @@ -222,13 +174,15 @@ class TestRequestsIntegration(TestBase): ) self.assert_metric_expected( client_response_size, - result.length, - { - "http.status_code": str(result.code), - "http.method": "POST", - "http.url": str(result.url), - "http.flavor": "1.1", - }, + self.create_histogram_data_points( + result.length, + attributes={ + "http.status_code": str(result.code), + "http.method": "POST", + "http.url": str(result.url), + "http.flavor": "1.1", + }, + ), ) def test_metric_uninstrument(self): diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py index d47cd8f1e..5baddee51 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py @@ -12,11 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from timeit import default_timer - import urllib3 import urllib3.exceptions -from urllib3.request import encode_multipart_formdata from opentelemetry import trace from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor @@ -87,136 +84,3 @@ class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase): "net.peer.ip": self.assert_ip, } self.assertGreaterEqual(span.attributes.items(), attributes.items()) - - -class TestURLLib3InstrumentorMetric(HttpTestBase, TestBase): - def setUp(self): - super().setUp() - self.assert_ip = self.server.server_address[0] - self.assert_port = self.server.server_address[1] - self.http_host = ":".join(map(str, self.server.server_address[:2])) - self.http_url_base = "http://" + self.http_host - self.http_url = self.http_url_base + "/status/200" - URLLib3Instrumentor().instrument(meter_provider=self.meter_provider) - - def tearDown(self): - super().tearDown() - URLLib3Instrumentor().uninstrument() - - def test_metric_uninstrument(self): - with urllib3.PoolManager() as pool: - pool.request("GET", self.http_url) - URLLib3Instrumentor().uninstrument() - pool.request("GET", self.http_url) - - metrics_list = self.memory_metrics_reader.get_metrics_data() - for resource_metric in metrics_list.resource_metrics: - for scope_metric in resource_metric.scope_metrics: - for metric in scope_metric.metrics: - for point in list(metric.data.data_points): - self.assertEqual(point.count, 1) - - def test_basic_metric_check_client_size_get(self): - with urllib3.PoolManager() as pool: - start_time = default_timer() - response = pool.request("GET", self.http_url) - client_duration_estimated = (default_timer() - start_time) * 1000 - - expected_attributes = { - "http.status_code": 200, - "http.host": self.assert_ip, - "http.method": "GET", - "http.flavor": "1.1", - "http.scheme": "http", - "net.peer.name": self.assert_ip, - "net.peer.port": self.assert_port, - } - expected_data = { - "http.client.request.size": 0, - "http.client.response.size": len(response.data), - } - expected_metrics = [ - "http.client.duration", - "http.client.request.size", - "http.client.response.size", - ] - - resource_metrics = ( - self.memory_metrics_reader.get_metrics_data().resource_metrics - ) - for metrics in resource_metrics: - for scope_metrics in metrics.scope_metrics: - self.assertEqual(len(scope_metrics.metrics), 3) - for metric in scope_metrics.metrics: - for data_point in metric.data.data_points: - if metric.name in expected_data: - self.assertEqual( - data_point.sum, expected_data[metric.name] - ) - if metric.name == "http.client.duration": - self.assertAlmostEqual( - data_point.sum, - client_duration_estimated, - delta=1000, - ) - self.assertIn(metric.name, expected_metrics) - self.assertDictEqual( - expected_attributes, - dict(data_point.attributes), - ) - self.assertEqual(data_point.count, 1) - - def test_basic_metric_check_client_size_post(self): - with urllib3.PoolManager() as pool: - start_time = default_timer() - data_fields = {"data": "test"} - response = pool.request("POST", self.http_url, fields=data_fields) - client_duration_estimated = (default_timer() - start_time) * 1000 - - expected_attributes = { - "http.status_code": 501, - "http.host": self.assert_ip, - "http.method": "POST", - "http.flavor": "1.1", - "http.scheme": "http", - "net.peer.name": self.assert_ip, - "net.peer.port": self.assert_port, - } - - body = encode_multipart_formdata(data_fields)[0] - - expected_data = { - "http.client.request.size": len(body), - "http.client.response.size": len(response.data), - } - expected_metrics = [ - "http.client.duration", - "http.client.request.size", - "http.client.response.size", - ] - - resource_metrics = ( - self.memory_metrics_reader.get_metrics_data().resource_metrics - ) - for metrics in resource_metrics: - for scope_metrics in metrics.scope_metrics: - self.assertEqual(len(scope_metrics.metrics), 3) - for metric in scope_metrics.metrics: - for data_point in metric.data.data_points: - if metric.name in expected_data: - self.assertEqual( - data_point.sum, expected_data[metric.name] - ) - if metric.name == "http.client.duration": - self.assertAlmostEqual( - data_point.sum, - client_duration_estimated, - delta=1000, - ) - self.assertIn(metric.name, expected_metrics) - - self.assertDictEqual( - expected_attributes, - dict(data_point.attributes), - ) - self.assertEqual(data_point.count, 1) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_metrics.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_metrics.py new file mode 100644 index 000000000..203bf537d --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_metrics.py @@ -0,0 +1,203 @@ +# Copyright The OpenTelemetry 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. + +from timeit import default_timer + +import urllib3 +import urllib3.exceptions +from urllib3.request import encode_multipart_formdata + +from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor +from opentelemetry.test.httptest import HttpTestBase +from opentelemetry.test.test_base import TestBase + + +class TestUrllib3MetricsInstrumentation(HttpTestBase, TestBase): + def setUp(self): + super().setUp() + self.assert_ip = self.server.server_address[0] + self.assert_port = self.server.server_address[1] + self.http_host = ":".join(map(str, self.server.server_address[:2])) + self.http_url_base = "http://" + self.http_host + self.http_url = self.http_url_base + "/status/200" + URLLib3Instrumentor().instrument(meter_provider=self.meter_provider) + + def tearDown(self): + super().tearDown() + URLLib3Instrumentor().uninstrument() + + # Return Sequence with one histogram + def create_histogram_data_points(self, sum_data_point, attributes): + return [ + self.create_histogram_data_point( + sum_data_point, 1, sum_data_point, sum_data_point, attributes + ) + ] + + def test_basic_metric_check_client_size_get(self): + with urllib3.PoolManager() as pool: + start_time = default_timer() + response = pool.request("GET", self.http_url) + client_duration_estimated = (default_timer() - start_time) * 1000 + + metrics = self.get_sorted_metrics() + + ( + client_duration, + client_request_size, + client_response_size, + ) = metrics + + self.assertEqual(client_duration.name, "http.client.duration") + self.assert_metric_expected( + client_duration, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.status_code": 200, + "http.host": self.assert_ip, + "http.method": "GET", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + est_value_delta=200, + ) + + self.assertEqual( + client_request_size.name, "http.client.request.size" + ) + self.assert_metric_expected( + client_request_size, + self.create_histogram_data_points( + 0, + attributes={ + "http.status_code": 200, + "http.host": self.assert_ip, + "http.method": "GET", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + ) + + self.assertEqual( + client_response_size.name, "http.client.response.size" + ) + self.assert_metric_expected( + client_response_size, + self.create_histogram_data_points( + len(response.data), + attributes={ + "http.status_code": 200, + "http.host": self.assert_ip, + "http.method": "GET", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + ) + + def test_basic_metric_check_client_size_post(self): + with urllib3.PoolManager() as pool: + start_time = default_timer() + data_fields = {"data": "test"} + response = pool.request("POST", self.http_url, fields=data_fields) + client_duration_estimated = (default_timer() - start_time) * 1000 + body = encode_multipart_formdata(data_fields)[0] + + metrics = self.get_sorted_metrics() + + ( + client_duration, + client_request_size, + client_response_size, + ) = metrics + + self.assertEqual(client_duration.name, "http.client.duration") + self.assert_metric_expected( + client_duration, + self.create_histogram_data_points( + client_duration_estimated, + attributes={ + "http.status_code": 501, + "http.host": self.assert_ip, + "http.method": "POST", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + est_value_delta=200, + ) + + self.assertEqual( + client_request_size.name, "http.client.request.size" + ) + client_request_size_expected = len(body) + self.assert_metric_expected( + client_request_size, + self.create_histogram_data_points( + client_request_size_expected, + attributes={ + "http.status_code": 501, + "http.host": self.assert_ip, + "http.method": "POST", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + ) + + self.assertEqual( + client_response_size.name, "http.client.response.size" + ) + client_response_size_expected = len(response.data) + self.assert_metric_expected( + client_response_size, + self.create_histogram_data_points( + client_response_size_expected, + attributes={ + "http.status_code": 501, + "http.host": self.assert_ip, + "http.method": "POST", + "http.flavor": "1.1", + "http.scheme": "http", + "net.peer.name": self.assert_ip, + "net.peer.port": self.assert_port, + }, + ), + ) + + def test_metric_uninstrument(self): + with urllib3.PoolManager() as pool: + pool.request("GET", self.http_url) + URLLib3Instrumentor().uninstrument() + pool.request("GET", self.http_url) + + metrics = self.get_sorted_metrics() + self.assertEqual(len(metrics), 3) + + for metric in metrics: + for point in list(metric.data.data_points): + self.assertEqual(point.count, 1)