From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/builtin/intl/IcuMemoryUsage.java | 268 ++++++++++++++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 js/src/builtin/intl/IcuMemoryUsage.java (limited to 'js/src/builtin/intl/IcuMemoryUsage.java') diff --git a/js/src/builtin/intl/IcuMemoryUsage.java b/js/src/builtin/intl/IcuMemoryUsage.java new file mode 100644 index 0000000000..2295e2298e --- /dev/null +++ b/js/src/builtin/intl/IcuMemoryUsage.java @@ -0,0 +1,268 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.*; +import java.util.stream.Collectors; + +/** + * Java program to estimate the memory usage of ICU objects (bug 1585536). + * + * It computes for each Intl constructor the amount of allocated memory. We're + * currently using the maximum memory ("max" in the output) to estimate the + * memory consumption of ICU objects. + * + * Insert before {@code JS_InitWithFailureDiagnostic} in "js.cpp": + * + *
+ * 
+ * JS_SetICUMemoryFunctions(
+ *     [](const void*, size_t size) {
+ *       void* ptr = malloc(size);
+ *       if (ptr) {
+ *         printf("  alloc: %p -> %zu\n", ptr, size);
+ *       }
+ *       return ptr;
+ *     },
+ *     [](const void*, void* p, size_t size) {
+ *       void* ptr = realloc(p, size);
+ *       if (p) {
+ *         printf("  realloc: %p -> %p -> %zu\n", p, ptr, size);
+ *       } else {
+ *         printf("  alloc: %p -> %zu\n", ptr, size);
+ *       }
+ *       return ptr;
+ *     },
+ *     [](const void*, void* p) {
+ *       if (p) {
+ *         printf("  free: %p\n", p);
+ *       }
+ *       free(p);
+ *     });
+ * 
+ * 
+ * + * Run this script with: + * {@code java IcuMemoryUsage.java $MOZ_JS_SHELL}. + */ +@SuppressWarnings("preview") +public class IcuMemoryUsage { + private enum Phase { + None, Create, Init, Destroy, Collect, Quit + } + + private static final class Memory { + private Phase phase = Phase.None; + private HashMap> allocations = new HashMap<>(); + private HashSet freed = new HashSet<>(); + private HashMap> completeAllocations = new HashMap<>(); + private int allocCount = 0; + private ArrayList allocSizes = new ArrayList<>(); + + void transition(Phase nextPhase) { + assert phase.ordinal() + 1 == nextPhase.ordinal() || (phase == Phase.Collect && nextPhase == Phase.Create); + phase = nextPhase; + + // Create a clean slate when starting a new create cycle or before termination. + if (phase == Phase.Create || phase == Phase.Quit) { + transferAllocations(); + } + + // Only measure the allocation size when creating the second object with the + // same locale. + if (phase == Phase.Collect && ++allocCount % 2 == 0) { + long size = allocations.values().stream().map(Map.Entry::getValue).reduce(0L, (a, c) -> a + c); + allocSizes.add(size); + } + } + + void transferAllocations() { + completeAllocations.putAll(allocations); + completeAllocations.keySet().removeAll(freed); + allocations.clear(); + freed.clear(); + } + + void alloc(long ptr, long size) { + allocations.put(ptr, Map.entry(phase, size)); + } + + void realloc(long oldPtr, long newPtr, long size) { + free(oldPtr); + allocations.put(newPtr, Map.entry(phase, size)); + } + + void free(long ptr) { + if (allocations.remove(ptr) == null) { + freed.add(ptr); + } + } + + LongSummaryStatistics statistics() { + return allocSizes.stream().collect(Collectors.summarizingLong(Long::valueOf)); + } + + double percentile(double p) { + var size = allocSizes.size(); + return allocSizes.stream().sorted().skip((long) ((size - 1) * p)).limit(2 - size % 2) + .mapToDouble(Long::doubleValue).average().getAsDouble(); + } + + long persistent() { + return completeAllocations.values().stream().map(Map.Entry::getValue).reduce(0L, (a, c) -> a + c); + } + } + + private static long parseSize(Matcher m, int group) { + return Long.parseLong(m.group(group), 10); + } + + private static long parsePointer(Matcher m, int group) { + return Long.parseLong(m.group(group), 16); + } + + private static void measure(String exec, String constructor, String description, String initializer) throws IOException { + var pb = new ProcessBuilder(exec, "--file=-", "--", constructor, initializer); + var process = pb.start(); + + try (var writer = new BufferedWriter( + new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8))) { + writer.write(sourceCode); + writer.flush(); + } + + var memory = new Memory(); + + try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + var reAlloc = Pattern.compile("\\s+alloc: 0x(\\p{XDigit}+) -> (\\p{Digit}+)"); + var reRealloc = Pattern.compile("\\s+realloc: 0x(\\p{XDigit}+) -> 0x(\\p{XDigit}+) -> (\\p{Digit}+)"); + var reFree = Pattern.compile("\\s+free: 0x(\\p{XDigit}+)"); + + String line; + while ((line = reader.readLine()) != null) { + Matcher m; + if ((m = reAlloc.matcher(line)).matches()) { + var ptr = parsePointer(m, 1); + var size = parseSize(m, 2); + memory.alloc(ptr, size); + } else if ((m = reRealloc.matcher(line)).matches()) { + var oldPtr = parsePointer(m, 1); + var newPtr = parsePointer(m, 2); + var size = parseSize(m, 3); + memory.realloc(oldPtr, newPtr, size); + } else if ((m = reFree.matcher(line)).matches()) { + var ptr = parsePointer(m, 1); + memory.free(ptr); + } else { + memory.transition(Phase.valueOf(line)); + } + } + } + + try (var errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { + String line; + while ((line = errorReader.readLine()) != null) { + System.err.println(line); + } + } + + var stats = memory.statistics(); + + System.out.printf("%s%n", description); + System.out.printf(" max: %d%n", stats.getMax()); + System.out.printf(" min: %d%n", stats.getMin()); + System.out.printf(" avg: %.0f%n", stats.getAverage()); + System.out.printf(" 50p: %.0f%n", memory.percentile(0.50)); + System.out.printf(" 75p: %.0f%n", memory.percentile(0.75)); + System.out.printf(" 85p: %.0f%n", memory.percentile(0.85)); + System.out.printf(" 95p: %.0f%n", memory.percentile(0.95)); + System.out.printf(" 99p: %.0f%n", memory.percentile(0.99)); + System.out.printf(" mem: %d%n", memory.persistent()); + + memory.transferAllocations(); + assert memory.persistent() == 0 : String.format("Leaked %d bytes", memory.persistent()); + } + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + throw new RuntimeException("The first argument must point to the SpiderMonkey shell executable"); + } + + record Entry (String constructor, String description, String initializer) { + public static Entry of(String constructor, String description, String initializer) { + return new Entry(constructor, description, initializer); + } + + public static Entry of(String constructor, String initializer) { + return new Entry(constructor, constructor, initializer); + } + } + + var objects = new ArrayList(); + objects.add(Entry.of("Intl.Collator", "o.compare('a', 'b')")); + objects.add(Entry.of("Intl.DateTimeFormat", "DateTimeFormat (UDateFormat)", "o.format(0)")); + objects.add(Entry.of("Intl.DateTimeFormat", "DateTimeFormat (UDateFormat+UDateIntervalFormat)", + "o.formatRange(0, 24*60*60*1000)")); + objects.add(Entry.of("Intl.DisplayNames", "o.of('en')")); + objects.add(Entry.of("Intl.ListFormat", "o.format(['a', 'b'])")); + objects.add(Entry.of("Intl.NumberFormat", "o.format(0)")); + objects.add(Entry.of("Intl.NumberFormat", "NumberFormat (UNumberRangeFormatter)", + "o.formatRange(0, 1000)")); + objects.add(Entry.of("Intl.PluralRules", "o.select(0)")); + objects.add(Entry.of("Intl.RelativeTimeFormat", "o.format(0, 'hour')")); + objects.add(Entry.of("Temporal.TimeZone", "o.getNextTransition(new Temporal.Instant(0n))")); + + for (var entry : objects) { + measure(args[0], entry.constructor, entry.description, entry.initializer); + } + } + + private static final String sourceCode = """ +const constructorName = scriptArgs[0]; +const initializer = Function("o", scriptArgs[1]); + +const extras = {}; +addIntlExtras(extras); + +let constructor; +let inputs; +if (constructorName.startsWith("Intl.")) { + let simpleName = constructorName.substring("Intl.".length); + constructor = Intl[simpleName]; + inputs = getAvailableLocalesOf(simpleName); +} else if (constructorName === "Temporal.TimeZone") { + constructor = Temporal.TimeZone; + inputs = Intl.supportedValuesOf("timeZone"); +} else { + throw new Error("Unsupported constructor name: " + constructorName); +} + +for (let i = 0; i < inputs.length; ++i) { + // Loop twice in case the first time we create an object with a new locale + // allocates additional memory when loading the locale data. + for (let j = 0; j < 2; ++j) { + let options = undefined; + if (constructor === Intl.DisplayNames) { + options = {type: "language"}; + } + + print("Create"); + let obj = new constructor(inputs[i], options); + + print("Init"); + initializer(obj); + + print("Destroy"); + gc(); + gc(); + print("Collect"); + } +} + +print("Quit"); +quit(); +"""; +} -- cgit v1.2.3