blob: 8bd941c7d2c5d73fbbfca4b05fac4952a9e14a82 [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.
#
import threading
import timeit
from skywalking.meter.meter import BaseMeter, MeterType
from skywalking.protocol.language_agent.Meter_pb2 import MeterBucketValue, MeterData, MeterHistogram
class Histogram(BaseMeter):
def __init__(self, name: str, steps, min_value=0, tags=None):
super().__init__(name, tags)
self.min_value = min_value
# check if a list is empty?
# https://stackoverflow.com/a/53522/9845190
if not steps:
raise Exception('steps must not be empty')
# sort and deduplicate
# https://stackoverflow.com/a/2931683/9845190
steps = sorted(set(steps))
if steps[0] < self.min_value:
raise Exception('steps are invalid')
elif steps[0] != self.min_value:
steps.insert(0, self.min_value)
self.init_bucks(steps)
def add_value(self, value):
bucket = self.find_bucket(value)
if bucket is None:
return
bucket.increment(1)
def find_bucket(self, value):
left = 0
right = len(self.buckets)
# find the first bucket greater than or equal to the value
while left < right:
mid = (left + right) // 2
if self.buckets[mid].bucket < value:
left = mid + 1
else:
right = mid
left -= 1
return self.buckets[left] if left < len(self.buckets) and left >= 0 else None
def init_bucks(self, steps):
self.buckets = [Histogram.Bucket(step) for step in steps]
def transform(self):
values = [bucket.transform() for bucket in self.buckets]
return MeterData(histogram=MeterHistogram(name=self.get_name(), labels=self.transform_tags(), values=values))
def get_type(self):
return MeterType.HISTOGRAM
def create_timer(self):
return Histogram.Timer(self)
class Bucket():
def __init__(self, bucket):
self.bucket = bucket
self.count = 0
self._lock = threading.Lock()
def increment(self, count):
with self._lock:
self.count += count
def transform(self):
return MeterBucketValue(bucket=self.bucket, count=self.count)
def __hash__(self) -> int:
return hash((self.bucket, self.count))
class Timer():
def __init__(self, metrics):
self.metrics = metrics
def __enter__(self):
self.start = timeit.default_timer()
def __exit__(self, exc_type, exc_value, exc_tb):
self.stop = timeit.default_timer()
duration = self.stop - self.start
self.metrics.add_value(duration)
@staticmethod
def timer(name: str):
def inner(func):
def wrapper(*args, **kwargs):
start = timeit.default_timer()
func(*args, **kwargs)
stop = timeit.default_timer()
duration = stop - start
histogram = Histogram.meter_service.get_meter(name)
histogram.add_value(duration)
return wrapper
return inner
class Builder(BaseMeter.Builder):
def __init__(self, name: str, steps, min_value=0, tags=None):
self.meter = Histogram(name, steps, min_value, tags)