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

NH-64716 First steps to integrate oboe settings API #223

Merged
merged 22 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
48 changes: 36 additions & 12 deletions solarwinds_apm/apm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ def __init__(
)

# Calculate c-lib extension usage
# TODO Used returned OboeAPI
# https://swicloud.atlassian.net/browse/NH-64716
self.extension, self.context = self._get_extension_components(
(
self.extension,
self.context,
self.oboe_api,
) = self._get_extension_components(
self.agent_enabled,
self.is_lambda,
)
Expand All @@ -157,15 +159,17 @@ def _get_extension_components(
) -> None:
"""Returns c-lib extension or noop components based on agent_enabled, is_lambda.

TODO: Return OboeAPI as noop or c-lib version
https://swicloud.atlassian.net/browse/NH-64716

agent_enabled T, is_lambda F -> c-lib extension, c-lib Context
agent_enabled T, is_lambda T -> no-op extension, no-op Context
agent_enabled T, is_lambda F -> c-lib extension, c-lib Context, no-op settings API
agent_enabled T, is_lambda T -> no-op extension, no-op Context, c-lib settings API
agent_enabled F -> all no-op
"""
if not agent_enabled:
return noop_extension, noop_extension.Context
return (
noop_extension,
noop_extension.Context,
noop_extension.OboeAPI,
)

try:
# pylint: disable=import-outside-toplevel
import solarwinds_apm.extension.oboe as c_extension
Expand All @@ -177,11 +181,31 @@ def _get_extension_components(
INTL_SWO_SUPPORT_EMAIL,
err,
)
return noop_extension, noop_extension.Context
return (
noop_extension,
noop_extension.Context,
noop_extension.OboeAPI,
)

if is_lambda:
return noop_extension, noop_extension.Context
return c_extension, c_extension.Context
try:
# pylint: disable=import-outside-toplevel,no-name-in-module
from solarwinds_apm.extension.oboe import OboeAPI as oboe_api
except ImportError as err:
# c-lib version may not have settings API
# TODO remove this check later
logger.warning(
"Could not import API in lambda mode. Installed layer version not compatible. Tracing disabled: %s",
err,
)
return (
noop_extension,
noop_extension.Context,
noop_extension.OboeAPI,
)
Comment on lines +198 to +206
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what happens if APM Python is built using c-lib < 14.0.0

return noop_extension, noop_extension.Context, oboe_api

return c_extension, c_extension.Context, noop_extension.OboeAPI

@classmethod
def calculate_is_lambda(cls) -> bool:
Expand Down
132 changes: 129 additions & 3 deletions solarwinds_apm/apm_meter_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,147 @@

import logging

from opentelemetry import metrics
# TypeError: 'ABCMeta' object is not subscriptable
# with this old import for callback signatures, with current Otel API
# pylint:disable=deprecated-typing-alias
from typing import TYPE_CHECKING, Iterable

from opentelemetry.metrics import CallbackOptions, Observation, get_meter

if TYPE_CHECKING:
from solarwinds_apm.apm_config import SolarWindsApmConfig

logger = logging.getLogger(__name__)


class SolarWindsMeterManager:
"""SolarWinds Python OTLP Meter Manager"""

def __init__(self, **kwargs: int) -> None:
def __init__(
self, apm_config: "SolarWindsApmConfig", **kwargs: int
) -> None:
self.oboe_settings_api = apm_config.oboe_api()

# Returns named `Meter` to handle instrument creation.
# A convenience wrapper for MeterProvider.get_meter
self.meter_response_times = metrics.get_meter("sw.apm.request.metrics")
self.meter_response_times = get_meter("sw.apm.request.metrics")
self.meter_request_counters = get_meter("sw.apm.sampling.metrics")

self.response_time = self.meter_response_times.create_histogram(
name="trace.service.response_time",
description="measures the duration of an inbound HTTP request",
unit="ms",
)

def consume_tracecount(
options: CallbackOptions,
) -> Iterable[Observation]:
status, trace_count = self.oboe_settings_api.consumeTraceCount()
yield Observation(trace_count, {"status": status})

self.tracecount = self.meter_request_counters.create_observable_gauge(
name="trace.service.tracecount",
callbacks=[consume_tracecount],
)

def consume_samplecount(
options: CallbackOptions,
) -> Iterable[Observation]:
status, trace_count = self.oboe_settings_api.consumeSampleCount()
yield Observation(trace_count, {"status": status})

self.samplecount = self.meter_request_counters.create_observable_gauge(
name="trace.service.samplecount",
callbacks=[consume_samplecount],
)

def consume_request_count(
options: CallbackOptions,
) -> Iterable[Observation]:
status, trace_count = self.oboe_settings_api.consumeRequestCount()
yield Observation(trace_count, {"status": status})

self.request_count = (
self.meter_request_counters.create_observable_gauge(
name="trace.service.request_count",
callbacks=[consume_request_count],
)
)

def consume_tokenbucket_exhaustion_count(
options: CallbackOptions,
) -> Iterable[Observation]:
(
status,
trace_count,
) = self.oboe_settings_api.consumeTokenBucketExhaustionCount()
yield Observation(trace_count, {"status": status})

self.tokenbucket_exhaustion_count = (
self.meter_request_counters.create_observable_gauge(
name="trace.service.tokenbucket_exhaustion_count",
callbacks=[consume_tokenbucket_exhaustion_count],
)
)

def consume_through_trace_count(
options: CallbackOptions,
) -> Iterable[Observation]:
(
status,
trace_count,
) = self.oboe_settings_api.consumeThroughTraceCount()
yield Observation(trace_count, {"status": status})

self.through_trace_count = (
self.meter_request_counters.create_observable_gauge(
name="trace.service.through_trace_count",
callbacks=[consume_through_trace_count],
)
)

def consume_triggered_trace_count(
options: CallbackOptions,
) -> Iterable[Observation]:
(
status,
trace_count,
) = self.oboe_settings_api.consumeTriggeredTraceCount()
yield Observation(trace_count, {"status": status})

self.triggered_trace_count = (
self.meter_request_counters.create_observable_gauge(
name="trace.service.triggered_trace_count",
callbacks=[consume_triggered_trace_count],
)
)

def get_last_used_sample_rate(
options: CallbackOptions,
) -> Iterable[Observation]:
(
status,
trace_count,
) = self.oboe_settings_api.getLastUsedSampleRate()
yield Observation(trace_count, {"status": status})

self.sample_rate = self.meter_request_counters.create_observable_gauge(
name="trace.service.sample_rate",
callbacks=[get_last_used_sample_rate],
)

def get_last_used_sample_source(
options: CallbackOptions,
) -> Iterable[Observation]:
(
status,
trace_count,
) = self.oboe_settings_api.getLastUsedSampleSource()
yield Observation(trace_count, {"status": status})

self.sample_source = (
self.meter_request_counters.create_observable_gauge(
name="trace.service.sample_source",
callbacks=[get_last_used_sample_source],
)
)
44 changes: 44 additions & 0 deletions solarwinds_apm/apm_noop.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,47 @@ class SolarWindsMeterManager:
def __init__(self, *args, **kwargs):
self.meter = None
self.response_time = NoOpHistogram(name="trace.service.response_time")


class OboeAPI:
def __init__(self, *args, **kwargs):
pass

def getTracingDecision(self):
return (
0,
0,
0,
0,
0.0,
0.0,
0,
0,
"",
"",
0,
)

def consumeRequestCount(self):
return 0, 0

def consumeTokenBucketExhaustionCount(self):
return 0, 0

def consumeTraceCount(self):
return 0, 0

def consumeSampleCount(self):
return 0, 0

def consumeThroughTraceCount(self):
return 0, 0

def consumeTriggeredTraceCount(self):
return 0, 0

def getLastUsedSampleRate(self):
return 0, 0

def getLastUsedSampleSource(self):
return 0, 0
8 changes: 6 additions & 2 deletions solarwinds_apm/configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def _configure(self, **kwargs: int) -> None:
)
apm_meters = NoopMeterManager()
else:
apm_meters = SolarWindsMeterManager()
apm_meters = SolarWindsMeterManager(apm_config)

reporter = self._initialize_solarwinds_reporter(apm_config)
self._configure_otel_components(
Expand Down Expand Up @@ -333,7 +333,7 @@ def _configure_metrics_exporter(
resource = trace.get_tracer_provider().get_tracer(__name__).resource
sw_resource = Resource.create(
{
"sw.trace_span_mode": "otel",
"sw.data.module": "apm",
"service.name": apm_config.service_name,
}
).merge(resource)
Expand Down Expand Up @@ -541,6 +541,10 @@ def _report_init_event(
keys: dict = None,
) -> None:
"""Report the APM library's init message, when reporter ready."""
if apm_config.is_lambda:
logger.debug("Skipping init event in lambda")
return

reporter_ready = False
if reporter.init_status in (
OboeReporterCode.OBOE_INIT_OK,
Expand Down
25 changes: 3 additions & 22 deletions solarwinds_apm/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ def __init__(self, apm_config: "SolarWindsApmConfig"):
else:
self.tracing_mode = self._UNSET

if self.apm_config.is_lambda:
# TODO Init OboeAPI if apm_config.is_lambda
# https://swicloud.atlassian.net/browse/NH-64716
pass
self.oboe_settings_api = apm_config.oboe_api()

def get_description(self) -> str:
return "SolarWinds custom opentelemetry sampler"
Expand Down Expand Up @@ -175,11 +172,7 @@ def calculate_liboboe_decision(
timestamp = xtraceoptions.timestamp

if self.apm_config.is_lambda:
# TODO OboeAPI getTracingDecision
# https://swicloud.atlassian.net/browse/NH-64716
logger.warning(
"Sampling in lambda is not yet implemented. Dropping trace."
)
logger.debug("Sampling in lambda mode.")
(
do_metrics,
do_sample,
Expand All @@ -192,19 +185,7 @@ def calculate_liboboe_decision(
status_msg,
auth_msg,
status,
) = (
0,
0,
0,
0,
0.0,
0.0,
0,
0,
"",
"",
0,
)
) = self.oboe_settings_api.getTracingDecision()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the arguments to this call not matter ? Actually curious if passing it the same thing as the non-lambda version vs passing it nothing makes any difference or if I should be doing it differently on my side of things

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good question. Tried it now and yes, this method does not explode if passed all the same non-lambda args (though it's a drop decision). Works if no args at all of course.

It does error out if I try other combinations of args:Wrong number or type of arguments for overloaded function


else:
logger.debug(
Expand Down
Loading
Loading