blob: bfae1c6217a6f0011235053a915ea04e476fa1ea [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2023 gRPC authors.
#
# 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.
# USAGE:
# Run some tests with the GRPC_ARENA_TRACE_POOLED_ALLOCATIONS #define turned on.
# Capture the output to a text file.
# Invoke this program with that as an argument, and let it work its magic.
import collections
import heapq
import random
import re
import sys
# A single allocation, negative size => free
Allocation = collections.namedtuple('Allocation', 'size ptr')
Active = collections.namedtuple('Active', 'id size')
# Read through all the captures, and build up scrubbed traces
arenas = []
building = collections.defaultdict(list)
active = {}
biggest = 0
smallest = 1024
sizes = set()
for filename in sys.argv[1:]:
for line in open(filename):
m = re.search(r'ARENA 0x([0-9a-f]+) ALLOC ([0-9]+) @ 0x([0-9a-f]+)',
line)
if m:
size = int(m.group(2))
if size > biggest:
biggest = size
if size < smallest:
smallest = size
active[m.group(3)] = Active(m.group(1), size)
building[m.group(1)].append(size)
sizes.add(size)
m = re.search(r'FREE 0x([0-9a-f]+)', line)
if m:
# We may have spurious frees, so make sure there's an outstanding allocation
last = active.pop(m.group(1), None)
if last is not None:
building[last.id].append(-last.size)
m = re.search(r'DESTRUCT_ARENA 0x([0-9a-f]+)', line)
if m:
trace = building.pop(m.group(1), None)
if trace:
arenas.append(trace)
# Given a list of pool sizes, return which bucket an allocation should go into
def bucket(pool_sizes, size):
for bucket in sorted(pool_sizes):
if abs(size) <= bucket:
return bucket
# Given a list of pool sizes, determine the total outstanding bytes in the arena for once trace
def outstanding_bytes(pool_sizes, trace):
free_list = collections.defaultdict(int)
allocated = 0
for size in trace:
b = bucket(pool_sizes, size)
if size < 0:
free_list[b] += 1
else:
if free_list[b] > 0:
free_list[b] -= 1
else:
allocated += b
return allocated + len(pool_sizes) * 8
# Given a list of pool sizes, determine the maximum outstanding bytes for any seen trace
def measure(pool_sizes):
max_outstanding = 0
for trace in arenas:
max_outstanding = max(max_outstanding,
outstanding_bytes(pool_sizes, trace))
return max_outstanding
ALWAYS_INCLUDE = 1024
best = [ALWAYS_INCLUDE, biggest]
best_measure = measure(best)
testq = []
step = 0
def add(l):
global testq, best_measure, best
m = measure(l)
if m < best_measure:
best_measure = m
best = l
if l[-1] == smallest:
return
heapq.heappush(testq, (m, l))
add(best)
while testq:
top = heapq.heappop(testq)[1]
m = measure(top)
step += 1
if step % 1000 == 0:
print("iter %d; pending=%d; top=%r/%d" %
(step, len(testq), top, measure(top)))
for i in sizes:
if i >= top[-1]:
continue
add(top + [i])
print("SAW SIZES: %r" % sorted(list(sizes)))
print("BEST: %r" % list(reversed(best)))
print("BEST MEASURE: %d" % best_measure)