|
| 1 | +import numpy as np |
| 2 | +import matplotlib.pyplot as plt |
| 3 | +import random |
| 4 | + |
| 5 | +from collections import defaultdict |
| 6 | + |
| 7 | +from opentelemetry import metrics |
| 8 | +from opentelemetry.sdk.metrics import Counter, MeterProvider |
| 9 | +from opentelemetry.sdk.metrics.export.aggregate import SumAggregator |
| 10 | +from opentelemetry.sdk.metrics.export.controller import PushController |
| 11 | +from opentelemetry.sdk.metrics.export.in_memory_metrics_exporter import InMemoryMetricsExporter |
| 12 | +from opentelemetry.sdk.metrics.view import View, ViewConfig |
| 13 | + |
| 14 | +## set up opentelemetry |
| 15 | + |
| 16 | +# Sets the global MeterProvider instance |
| 17 | +metrics.set_meter_provider(MeterProvider()) |
| 18 | + |
| 19 | +meter = metrics.get_meter(__name__) |
| 20 | + |
| 21 | +# Export to a python list so we can do stats with the data |
| 22 | +exporter = InMemoryMetricsExporter() |
| 23 | + |
| 24 | +# instead of waiting for the controller to tick over time, we will just tick it ourselves |
| 25 | +controller = PushController(meter, exporter, 500) |
| 26 | + |
| 27 | +# Create the metric that we will use |
| 28 | +bytes_counter = meter.create_metric( |
| 29 | + name="bytes_counter", |
| 30 | + description="Number of bytes received by service", |
| 31 | + unit="By", |
| 32 | + value_type=int, |
| 33 | + metric_type=Counter, |
| 34 | +) |
| 35 | + |
| 36 | +# Every time interval we will collect 100 exemplars statistically (selected without bias) |
| 37 | +aggregator_config = {"num_exemplars": 100, "statistical_exemplars": True} |
| 38 | + |
| 39 | +# Assign a Sum aggregator to `bytes_counter` that collects exemplars |
| 40 | +counter_view = View( |
| 41 | + bytes_counter, |
| 42 | + SumAggregator(config=aggregator_config), |
| 43 | + label_keys=["environment"], |
| 44 | + config=ViewConfig.LABEL_KEYS, |
| 45 | +) |
| 46 | + |
| 47 | +meter.register_view(counter_view) |
| 48 | + |
| 49 | +## generate the random metric data |
| 50 | + |
| 51 | +def unknown_customer_calls(): |
| 52 | + """Generate customer call data to our application""" |
| 53 | + |
| 54 | + # set a random seed for consistency of data for example purposes |
| 55 | + np.random.seed(1) |
| 56 | + # Make exemplar selection consistent for example purposes |
| 57 | + random.seed(1) |
| 58 | + |
| 59 | + # customer 123 is a big user, and made 1000 requests in this timeframe |
| 60 | + requests = np.random.normal(1000, 250, 1000) # 1000 requests with average 1000 bytes, covariance 100 |
| 61 | + |
| 62 | + for request in requests: |
| 63 | + bytes_counter.add(int(request), {"environment": "production", "method": "REST", "customer_id": 123}) |
| 64 | + |
| 65 | + # customer 247 is another big user, making fewer, but bigger requests |
| 66 | + requests = np.random.normal(5000, 1250, 200) # 200 requests with average size of 5k bytes |
| 67 | + |
| 68 | + for request in requests: |
| 69 | + bytes_counter.add(int(request), {"environment": "production", "method": "REST", "customer_id": 247}) |
| 70 | + |
| 71 | + # There are many other smaller customers |
| 72 | + for customer_id in range(250): |
| 73 | + requests = np.random.normal(1000, 250, np.random.randint(1, 10)) |
| 74 | + method = "REST" if np.random.randint(2) else "gRPC" |
| 75 | + for request in requests: |
| 76 | + bytes_counter.add(int(request), {"environment": "production", "method": method, "customer_id": customer_id}) |
| 77 | + |
| 78 | +unknown_customer_calls() |
| 79 | + |
| 80 | +# Tick the controller so it sends metrics to the exporter |
| 81 | +controller.tick() |
| 82 | + |
| 83 | +# collect metrics from our exporter |
| 84 | +metric_data = exporter.get_exported_metrics() |
| 85 | + |
| 86 | +# get the exemplars from the bytes_in counter aggregator |
| 87 | +aggregator = metric_data[0].aggregator |
| 88 | +exemplars = aggregator.checkpoint_exemplars |
| 89 | + |
| 90 | +# Sum up the total bytes in per customer from all of the exemplars collected |
| 91 | +customer_bytes_map = defaultdict(int) |
| 92 | +for exemplar in exemplars: |
| 93 | + customer_bytes_map[exemplar.dropped_labels] += exemplar.value |
| 94 | + |
| 95 | + |
| 96 | +customer_bytes_list = sorted(list(customer_bytes_map.items()), key=lambda t: t[1], reverse=True) |
| 97 | + |
| 98 | +# Save our top 5 customers and sum all of the rest into "Others". |
| 99 | +top_5_customers = [("Customer {}".format(dict(val[0])["customer_id"]), val[1]) for val in customer_bytes_list[:5]] + [("Other Customers", sum([val[1] for val in customer_bytes_list[5:]]))] |
| 100 | + |
| 101 | +# unzip the data into X (sizes of each customer's contribution) and labels |
| 102 | +labels, X = zip(*top_5_customers) |
| 103 | + |
| 104 | +# create the chart with matplotlib and show it |
| 105 | +plt.pie(X, labels=labels) |
| 106 | +plt.show() |
| 107 | + |
| 108 | +# Estimate how many bytes customer 123 sent |
| 109 | +customer_123_bytes = customer_bytes_map[(("customer_id", 123), ("method", "REST"))] |
| 110 | + |
| 111 | +# Since the exemplars were randomly sampled, all sample_counts will be the same |
| 112 | +sample_count = exemplars[0].sample_count |
| 113 | +print("sample count", sample_count, "custmer", customer_123_bytes) |
| 114 | +full_customer_123_bytes = sample_count * customer_123_bytes |
| 115 | + |
| 116 | +# With seed == 1 we get 1008612 - quite close to the statistical mean of 1000000! (more exemplars would make this estimation even more accurate) |
| 117 | +print("Customer 123 sent about {} bytes this interval".format(int(full_customer_123_bytes))) |
| 118 | + |
| 119 | +# Determine the top 25 customers by how many bytes they sent in exemplars |
| 120 | +top_25_customers = customer_bytes_list[:25] |
| 121 | + |
| 122 | +# out of those 25 customers, determine how many used grpc, and come up with a ratio |
| 123 | +percent_grpc = len(list(filter(lambda customer_value: customer_value[0][1][1] == "gRPC", top_25_customers))) / len(top_25_customers) |
| 124 | + |
| 125 | +print("~{}% of the top 25 customers (by bytes in) used gRPC this interval".format(int(percent_grpc*100))) |
| 126 | + |
| 127 | +# Determine the 50th, 90th, and 99th percentile of byte size sent in |
| 128 | +quantiles = np.quantile([exemplar.value for exemplar in exemplars], [0.5, 0.9, 0.99]) |
| 129 | +print("50th Percentile Bytes In:", int(quantiles[0])) |
| 130 | +print("90th Percentile Bytes In:", int(quantiles[1])) |
| 131 | +print("99th Percentile Bytes In:", int(quantiles[2])) |
0 commit comments