Skip to content

Commit

Permalink
Sampler always adds sw.transaction to span attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
tammy-baylis-swi committed Feb 6, 2024
1 parent d3653ca commit 4e78a0b
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 0 deletions.
34 changes: 34 additions & 0 deletions solarwinds_apm/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
INTL_SWO_COMMA_W3C_SANITIZED,
INTL_SWO_EQUALS_W3C_SANITIZED,
INTL_SWO_TRACESTATE_KEY,
INTL_SWO_TRANSACTION_ATTR_KEY,
INTL_SWO_X_OPTIONS_KEY,
INTL_SWO_X_OPTIONS_RESPONSE_KEY,
)
Expand Down Expand Up @@ -69,6 +70,9 @@ def __init__(
oboe_api: "OboeAPI",
):
self.apm_config = apm_config
# SW_APM_TRANSACTION_NAME and AWS_LAMBDA_FUNCTION_NAME
self.env_transaction_name = apm_config.get("transaction_name")
self.lambda_function_name = apm_config.lambda_function_name
self.context = apm_config.extension.Context

if self.apm_config.get("tracing_mode") is not None:
Expand Down Expand Up @@ -415,6 +419,29 @@ def add_tracestate_capture_to_attributes_dict(
)
return attributes_dict

def calculate_otlp_transaction_name(
self,
) -> str:
"""Calculate transaction name for OTLP trace following this order
of decreasing precedence:
1. SW_APM_TRANSACTION_NAME
2. AWS_LAMBDA_FUNCTION_NAME
3. "unknown_service"
Note: spans at time of sampling decision/on_start will not
have access to SDK-set names, and spans at on_end are immutable.
"""
if self.env_transaction_name:
return self.env_transaction_name

if self.lambda_function_name:
return self.lambda_function_name

# To match SDK Resource default
# https://github.com/open-telemetry/opentelemetry-python/blob/f5fb6b1353929cf8039b1d38f97450866357d901/opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py#L175
return "unknown_service"

def calculate_attributes(
self,
span_name: str,
Expand Down Expand Up @@ -462,6 +489,13 @@ def calculate_attributes(
f"{decision['bucket_rate']}"
)

# Always (root or is_remote) set sw.transaction
# TODO Add experimental trace flag, clean up
# https://swicloud.atlassian.net/browse/NH-65067
new_attributes[INTL_SWO_TRANSACTION_ATTR_KEY] = (
self.calculate_otlp_transaction_name()
)

# sw.tracestate_parent_id is set if:
# 1. the future span is the entry span of a service
# 2. there exists a (remote) parent span
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/test_sampler/fixtures/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def config_get(param) -> Any:
return -1
elif param == "transaction_filters":
return []
elif param == "transaction_name":
return "foo-txn"
else:
return None

Expand Down Expand Up @@ -46,6 +48,7 @@ def fixture_swsampler(mocker):
"get": mock_get,
"extension": mock_extension,
"is_lambda": False,
"lambda_function_name": "foo-lambda",
}
)
return _SwSampler(mock_apm_config, mocker.Mock())
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/test_sampler/test_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#
# 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 typing import Any

import pytest

from opentelemetry.sdk.trace.sampling import (
Expand Down Expand Up @@ -199,11 +201,28 @@ def fixture_parent_span_context_valid_remote_no_tracestate():

class Test_SwSampler():
def test_init(self, mocker):
def config_get(param) -> Any:
if param == "transaction_name":
return "foo-txn"
else:
return "foo"

mock_get = mocker.Mock(
side_effect=config_get
)
mock_apm_config = mocker.Mock()
mock_apm_config.configure_mock(
**{
"get": mock_get,
"lambda_function_name": "foo-lambda",
}
)
mock_oboe_api = mocker.Mock()
sampler = _SwSampler(mock_apm_config, mock_oboe_api)
assert sampler.apm_config == mock_apm_config
assert sampler.oboe_settings_api == mock_oboe_api
assert sampler.env_transaction_name == "foo-txn"
assert sampler.lambda_function_name == "foo-lambda"

def test_calculate_liboboe_decision_is_lambda(
self,
Expand Down
18 changes: 18 additions & 0 deletions tests/unit/test_sampler/test_sampler_calculate_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ def test_decision_record_and_sample_with_sw_keys_and_custom_keys_no_tt_unsigned(
"SampleSource": -1,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_decision_auth_ok_with_sw_keys_and_custom_keys_no_tt_signed(
Expand All @@ -387,6 +388,7 @@ def test_decision_auth_ok_with_sw_keys_and_custom_keys_no_tt_signed(
"SampleSource": -1,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_decision_auth_failed_with_sw_keys_and_custom_keys_no_tt_signed(
Expand Down Expand Up @@ -427,6 +429,7 @@ def test_decision_auth_ok_with_sw_keys_and_custom_keys_and_signed_tt(
"TriggeredTrace": True,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_decision_auth_failed_with_sw_keys_and_custom_keys_and_signed_tt(
Expand Down Expand Up @@ -467,6 +470,7 @@ def test_contd_decision_sw_keys_and_custom_keys_and_unsigned_tt(
"TriggeredTrace": True,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_contd_decision_sw_keys_and_custom_keys_and_signed_tt(
Expand All @@ -491,6 +495,7 @@ def test_contd_decision_sw_keys_and_custom_keys_and_signed_tt(
"TriggeredTrace": True,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_unsigned(
Expand All @@ -512,6 +517,7 @@ def test_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_unsigned(
"BucketRate": "-1",
"SampleRate": -1,
"SampleSource": -1,
"sw.transaction": "foo-txn",
})

def test_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_signed(
Expand All @@ -533,6 +539,7 @@ def test_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_signed(
"BucketRate": "-1",
"SampleRate": -1,
"SampleSource": -1,
"sw.transaction": "foo-txn",
})

def test_contd_decision_with_no_sw_keys_nor_custom_keys_with_unsigned_tt(
Expand All @@ -555,6 +562,7 @@ def test_contd_decision_with_no_sw_keys_nor_custom_keys_with_unsigned_tt(
"SampleRate": -1,
"SampleSource": -1,
"TriggeredTrace": True,
"sw.transaction": "foo-txn",
})

def test_contd_decision_with_no_sw_keys_nor_custom_keys_with_signed_tt(
Expand All @@ -577,6 +585,7 @@ def test_contd_decision_with_no_sw_keys_nor_custom_keys_with_signed_tt(
"SampleRate": -1,
"SampleSource": -1,
"TriggeredTrace": True,
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_sw_keys_and_custom_keys_and_unsigned_tt(
Expand All @@ -601,6 +610,7 @@ def test_not_contd_decision_with_sw_keys_and_custom_keys_and_unsigned_tt(
"TriggeredTrace": True,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_sw_keys_and_custom_keys_and_signed_tt(
Expand All @@ -625,6 +635,7 @@ def test_not_contd_decision_with_sw_keys_and_custom_keys_and_signed_tt(
"TriggeredTrace": True,
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_with_unsigned_tt(
Expand All @@ -647,6 +658,7 @@ def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_with_unsigned_tt(
"SampleRate": 1,
"SampleSource": 1,
"TriggeredTrace": True,
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_with_signed_tt(
Expand All @@ -669,6 +681,7 @@ def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_with_signed_tt(
"SampleRate": 1,
"SampleSource": 1,
"TriggeredTrace": True,
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_unsigned(
Expand All @@ -690,6 +703,7 @@ def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_unsigned(
"BucketRate": "1",
"SampleRate": 1,
"SampleSource": 1,
"sw.transaction": "foo-txn",
})

def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_signed(
Expand All @@ -711,6 +725,7 @@ def test_not_contd_decision_with_no_sw_keys_nor_custom_keys_nor_tt_signed(
"BucketRate": "1",
"SampleRate": 1,
"SampleSource": 1,
"sw.transaction": "foo-txn",
})

def test_valid_parent_create_new_attrs_with_sw_keys_and_custom_keys_and_unsigned_tt(
Expand Down Expand Up @@ -738,6 +753,7 @@ def test_valid_parent_create_new_attrs_with_sw_keys_and_custom_keys_and_unsigned
"sw.w3c.tracestate": "foo=bar,sw=123,baz=qux",
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_valid_parent_create_new_attrs_with_sw_keys_and_custom_keys_and_signed_tt(
Expand Down Expand Up @@ -765,6 +781,7 @@ def test_valid_parent_create_new_attrs_with_sw_keys_and_custom_keys_and_signed_t
"sw.w3c.tracestate": "foo=bar,sw=123,baz=qux",
"SWKeys": "foo",
"custom-foo": "awesome-bar",
"sw.transaction": "foo-txn",
})

def test_valid_parent_update_attrs_no_tracestate_capture_with_sw_keys_and_custom_keys_and_unsigned_tt(
Expand Down Expand Up @@ -921,4 +938,5 @@ def test_no_parent_update_attrs_no_tracestate(
"SampleSource": 6,
"foo": "bar",
"baz": "qux",
"sw.transaction": "foo-txn",
})
77 changes: 77 additions & 0 deletions tests/unit/test_sampler/test_sampler_calculate_otlp_tname.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# © 2024 SolarWinds Worldwide, LLC. All rights reserved.
#
# 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 typing import Any

import pytest

Check notice

Code scanning / CodeQL

Unused import Note test

Import of 'pytest' is not used.

from solarwinds_apm.sampler import _SwSampler





class Test_SwSampler_calculate_otlp_tname():

def test_calculate_otlp_name_env_var(self, mocker):

def config_get(param) -> Any:
if param == "transaction_name":
return "foo-txn"
else:
return "foo"

mock_get = mocker.Mock(
side_effect=config_get
)
mock_apm_config = mocker.Mock()
mock_apm_config.configure_mock(
**{
"get": mock_get,
"lambda_function_name": "foo-lambda",
}
)
mock_oboe_api = mocker.Mock()
sampler = _SwSampler(mock_apm_config, mock_oboe_api)
assert sampler.calculate_otlp_transaction_name() == "foo-txn"

def test_calculate_otlp_name_lambda(self, mocker):

def config_get(param) -> Any:
return None

mock_get = mocker.Mock(
side_effect=config_get
)
mock_apm_config = mocker.Mock()
mock_apm_config.configure_mock(
**{
"get": mock_get,
"lambda_function_name": "foo-lambda",
}
)
mock_oboe_api = mocker.Mock()
sampler = _SwSampler(mock_apm_config, mock_oboe_api)
assert sampler.calculate_otlp_transaction_name() == "foo-lambda"

def test_calculate_otlp_name_none(self, mocker):

def config_get(param) -> Any:
return None

mock_get = mocker.Mock(
side_effect=config_get
)
mock_apm_config = mocker.Mock()
mock_apm_config.configure_mock(
**{
"get": mock_get,
"lambda_function_name": None,
}
)
mock_oboe_api = mocker.Mock()
sampler = _SwSampler(mock_apm_config, mock_oboe_api)
assert sampler.calculate_otlp_transaction_name() == "unknown_service"

0 comments on commit 4e78a0b

Please sign in to comment.