skywalking/meter/histogram.py (77 lines of code) (raw):

# # 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)