From a474c7d7cc8cc443c1174480d9674b8846a2205f Mon Sep 17 00:00:00 2001 From: littleGnAl Date: Tue, 1 Oct 2024 18:19:30 +0800 Subject: [PATCH] chore!: Remove GlanceConfiguration.modulePathFilters and optimize the performance of aggregateStacks choreRemove GlanceConfiguration.modulePathFilters and optimize the performance of aggregateStacks --- lib/src/glance.dart | 14 +-- lib/src/glance_impl.dart | 2 - lib/src/sampler.dart | 91 ++++++++-------- test/glance_test.dart | 28 ----- test/sampler_test.dart | 219 +-------------------------------------- 5 files changed, 47 insertions(+), 307 deletions(-) delete mode 100644 test/glance_test.dart diff --git a/lib/src/glance.dart b/lib/src/glance.dart index 2a0c4e3..c8e6c6f 100644 --- a/lib/src/glance.dart +++ b/lib/src/glance.dart @@ -68,7 +68,7 @@ class GlanceConfiguration { this.reporters = const [], List modulePathFilters = const [], this.sampleRateInMilliseconds = kDefaultSampleRateInMilliseconds, - }) : _modulePathFilters = modulePathFilters; + }); /// The threshold in milliseconds for detecting UI jank. Defaults to [kDefaultJankThreshold]. final int jankThreshold; @@ -76,18 +76,6 @@ class GlanceConfiguration { /// A list of reporters that will handle the reporting of UI jank. final List reporters; - /// Filters with regex patterns for module paths. - /// If not set, [kAndroidDefaultModulePathFilters] is used on Android, and [kIOSDefaultModulePathFilters] is used on iOS. - final List _modulePathFilters; - List get modulePathFilters { - if (_modulePathFilters.isNotEmpty) { - return _modulePathFilters; - } - return defaultTargetPlatform == TargetPlatform.android - ? kAndroidDefaultModulePathFilters - : kIOSDefaultModulePathFilters; - } - /// The interval in milliseconds for capture the stack traces. Defaults to [kDefaultSampleRateInMilliseconds]. /// Lower value will capture more accuracy stack traces, but will impace the performance. final int sampleRateInMilliseconds; diff --git a/lib/src/glance_impl.dart b/lib/src/glance_impl.dart index 75ce51c..9b915f7 100644 --- a/lib/src/glance_impl.dart +++ b/lib/src/glance_impl.dart @@ -44,11 +44,9 @@ class GlanceImpl implements Glance { final jankThreshold = config.jankThreshold; final sampleRateInMilliseconds = config.sampleRateInMilliseconds; _reporters = List.of(config.reporters, growable: false); - final List modulePathFilters = config.modulePathFilters; _sampler ??= await Sampler.create(SamplerConfig( jankThreshold: jankThreshold, - modulePathFilters: modulePathFilters, sampleRateInMilliseconds: sampleRateInMilliseconds, )); diff --git a/lib/src/sampler.dart b/lib/src/sampler.dart index 1625a31..c613134 100644 --- a/lib/src/sampler.dart +++ b/lib/src/sampler.dart @@ -35,15 +35,11 @@ class SamplerConfig { SamplerConfig({ required this.jankThreshold, this.sampleRateInMilliseconds = kDefaultSampleRateInMilliseconds, - required this.modulePathFilters, this.samplerProcessorFactory = _defaultSamplerProcessorFactory, }); final int jankThreshold; - /// e.g., libapp.so, libflutter.so - final List modulePathFilters; - final int sampleRateInMilliseconds; /// The factory used to create a [SamplerProcessor]. This allows us to inject @@ -200,21 +196,12 @@ class SamplerProcessor { SendPort sendPort, int messageId, List timestampRange, - ) { + ) async { assert(isRunning); assert(_buffer != null, 'Make sure you call `loop` first'); - final args = [sendPort, _config, _buffer!, timestampRange, messageId]; - return compute((args) { - final sendPort = (args as List)[0] as SendPort; - final config = args[1] as SamplerConfig; - final buffer = args[2] as RingBuffer; - final timestampRange = args[3] as List; - final id = args[4] as int; - - final stacktrace = aggregateStacks(config, buffer, timestampRange); - sendPort.send(GetSamplesResponse(id, stacktrace)); - }, args); + final stacktrace = aggregateStacks(_config, _buffer!, timestampRange); + sendPort.send(GetSamplesResponse(messageId, stacktrace)); } /// Start an infinite loop to capture the [NativeStack] at intervals specified @@ -255,22 +242,9 @@ class SamplerProcessor { ) { void addOrUpdateAggregatedNativeFrame( SamplerConfig config, - List timestampRange, LinkedHashMap aggregatedFrameMap, NativeFrame frame) { - List modulePathFilters = config.modulePathFilters; - - int start = timestampRange[0]; - int end = timestampRange[1]; - - final isInclude = frame.module != null && - frame.timestamp >= start && - frame.timestamp <= end && - modulePathFilters.any((pathFilter) { - return RegExp(pathFilter).hasMatch(frame.module!.path); - }); - - if (!isInclude) { + if (frame.module == null) { return; } final pc = frame.pc; @@ -285,17 +259,28 @@ class SamplerProcessor { } } + int startTimestamp = timestampRange[0]; + int endTimestamp = timestampRange[1]; + final maxOccurTimes = config.jankThreshold / config.sampleRateInMilliseconds; final parentFrameMap = LinkedHashMap>.identity(); - for (final nativeStack in buffer.readAll().reversed) { - if (nativeStack?.frames.isEmpty == true) { + for (final nativeStack in buffer.readAllReversed()) { + if (nativeStack.frames.isEmpty) { + continue; + } + + final parentFrame = nativeStack.frames.last; + bool isInclude = parentFrame.timestamp >= startTimestamp && + parentFrame.timestamp <= endTimestamp; + if (!isInclude) { continue; } - int parentFramePc = nativeStack!.frames.last.pc; + + int parentFramePc = parentFrame.pc; bool isContainParentFrame = parentFrameMap.containsKey(parentFramePc); if (isContainParentFrame) { @@ -305,7 +290,7 @@ class SamplerProcessor { // Aggregate from parent. for (int i = frames.length - 1; i >= 0; --i) { addOrUpdateAggregatedNativeFrame( - config, timestampRange, aggregatedFrameMap, frames[i]); + config, aggregatedFrameMap, frames[i]); } } else { final aggregatedFrameMap = @@ -313,23 +298,23 @@ class SamplerProcessor { final frames = nativeStack.frames; for (int i = frames.length - 1; i >= 0; --i) { addOrUpdateAggregatedNativeFrame( - config, timestampRange, aggregatedFrameMap, frames[i]); + config, aggregatedFrameMap, frames[i]); } parentFrameMap.putIfAbsent(parentFramePc, () => aggregatedFrameMap); } } - final allFrameList = []; + List allFrameList = []; for (final entry in parentFrameMap.entries) { - final jankFrames = - entry.value.values.where((e) => e.occurTimes > maxOccurTimes); - if (jankFrames.isNotEmpty) { - allFrameList.addAll(jankFrames.toList().reversed); - } - } + for (final jankFrame in entry.value.values.toList().reversed) { + if (jankFrame.occurTimes > maxOccurTimes) { + allFrameList.add(jankFrame); + } - if (allFrameList.length > kMaxStackTraces) { - return allFrameList.sublist(0, kMaxStackTraces); + if (allFrameList.length >= kMaxStackTraces) { + return allFrameList; + } + } } return allFrameList; @@ -373,13 +358,21 @@ class RingBuffer { return value; } - List readAll() { - List result = []; - int current = _head; + List readAllReversed() { + List result = []; + int current = _tail == 0 ? _buffer.length - 1 : _tail - 1; while (current != _tail || (_isFull && result.length < _buffer.length)) { - result.add(_buffer[current]); - current = (current + 1) % _buffer.length; + final buffer = _buffer[current]; + if (buffer != null) { + result.add(buffer); + } + if (current == _head) { + break; // Stop if we've reached the _head + } + + current = + (current - 1 + _buffer.length) % _buffer.length; // Move backward } return result; diff --git a/test/glance_test.dart b/test/glance_test.dart deleted file mode 100644 index 3f93705..0000000 --- a/test/glance_test.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:glance/glance.dart'; - -void main() { - group('GlanceConfiguration get modulePathFilters', () { - tearDown(() { - debugDefaultTargetPlatformOverride = null; - }); - - test('has set modulePathFilters', () { - const config = GlanceConfiguration(modulePathFilters: ['hello']); - expect(config.modulePathFilters, equals(['hello'])); - }); - - test('has not set modulePathFilters on Android', () { - debugDefaultTargetPlatformOverride = TargetPlatform.android; - const config = GlanceConfiguration(); - expect(config.modulePathFilters, kAndroidDefaultModulePathFilters); - }); - - test('has not set modulePathFilters on iOS', () { - debugDefaultTargetPlatformOverride = TargetPlatform.iOS; - const config = GlanceConfiguration(); - expect(config.modulePathFilters, kIOSDefaultModulePathFilters); - }); - }); -} diff --git a/test/sampler_test.dart b/test/sampler_test.dart index 23f1051..30d04a4 100644 --- a/test/sampler_test.dart +++ b/test/sampler_test.dart @@ -92,7 +92,6 @@ void main() { sampler = await Sampler.create(SamplerConfig( jankThreshold: 1, - modulePathFilters: [], samplerProcessorFactory: _samplerProcessorFactory(processor.receivePort.sendPort, []), )); @@ -121,7 +120,6 @@ void main() { sampler = await Sampler.create(SamplerConfig( jankThreshold: 1, - modulePathFilters: [], samplerProcessorFactory: _samplerProcessorFactory(processor.receivePort.sendPort, [frame]), )); @@ -141,7 +139,6 @@ void main() { sampler = await Sampler.create(SamplerConfig( jankThreshold: 1, - modulePathFilters: [], samplerProcessorFactory: _samplerProcessorFactory(processor.receivePort.sendPort, []), )); @@ -160,7 +157,6 @@ void main() { sampler = await Sampler.create(SamplerConfig( jankThreshold: 1, - modulePathFilters: [], samplerProcessorFactory: _samplerProcessorFactory(processor.receivePort.sendPort, []), )); @@ -178,9 +174,8 @@ void main() { test('setCurrentThreadAsTarget', () { stackCapturer = FakeStackCapturer(); - samplerProcessor = SamplerProcessor( - SamplerConfig(jankThreshold: 1, modulePathFilters: []), - stackCapturer); + samplerProcessor = + SamplerProcessor(SamplerConfig(jankThreshold: 1), stackCapturer); samplerProcessor.setCurrentThreadAsTarget(); expect(stackCapturer.isSetCurrentThreadAsTarget, isTrue); }); @@ -191,7 +186,6 @@ void main() { samplerProcessor = SamplerProcessor( SamplerConfig( jankThreshold: 1, - modulePathFilters: [], sampleRateInMilliseconds: 1000, ), stackCapturer, @@ -246,7 +240,6 @@ void main() { samplerProcessor = SamplerProcessor( SamplerConfig( jankThreshold: 1, - modulePathFilters: [], sampleRateInMilliseconds: 1000, ), stackCapturer, @@ -294,7 +287,6 @@ void main() { samplerProcessor = SamplerProcessor( SamplerConfig( jankThreshold: 1, - modulePathFilters: [], sampleRateInMilliseconds: 1000, ), stackCapturer, @@ -344,7 +336,6 @@ void main() { samplerProcessor = SamplerProcessor( SamplerConfig( jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ), stackCapturer, @@ -422,7 +413,6 @@ void main() { samplerProcessor = SamplerProcessor( SamplerConfig( jankThreshold: 1, - modulePathFilters: [], sampleRateInMilliseconds: 1000, ), stackCapturer, @@ -436,7 +426,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ); samplerProcessor = SamplerProcessor( @@ -502,7 +491,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ); samplerProcessor = SamplerProcessor( @@ -570,7 +558,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ); samplerProcessor = SamplerProcessor( @@ -634,7 +621,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ); samplerProcessor = SamplerProcessor( @@ -698,7 +684,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 2, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1, ); samplerProcessor = SamplerProcessor( @@ -761,7 +746,6 @@ void main() { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 10, - modulePathFilters: kAndroidDefaultModulePathFilters, sampleRateInMilliseconds: 1, ); samplerProcessor = SamplerProcessor( @@ -791,141 +775,10 @@ void main() { expect(aggregatedNativeFrames.length, 0); }); - test('return filtered modules', () { - stackCapturer = FakeStackCapturer(); - final config = SamplerConfig( - jankThreshold: 1, - modulePathFilters: [ - r'(.*)libapp.so', - ], - sampleRateInMilliseconds: 1000, - ); - samplerProcessor = SamplerProcessor( - config, - stackCapturer, - ); - final now = Timeline.now; - final module1 = NativeModule( - id: 1, - path: 'libapp.so', - baseAddress: 540641718272, - symbolName: 'hello', - ); - final frame1 = NativeFrame( - pc: 540642472602, - timestamp: now - 5000, - module: module1, - ); - final module2 = NativeModule( - id: 2, - path: 'libapp.so', - baseAddress: 540641718272, - symbolName: 'world', - ); - final frame2 = NativeFrame( - pc: 540642472608, - timestamp: now - 4000, - module: module2, - ); - final module3 = NativeModule( - id: 3, - path: 'libflutter.so', - baseAddress: 540641718272, - symbolName: 'helloworld', - ); - final frame3 = NativeFrame( - pc: 540642472605, - timestamp: now - 3000, - module: module3, - ); - - final stack1 = NativeStack(frames: [frame1], modules: [module1]); - final stack2 = NativeStack(frames: [frame2], modules: [module2]); - final stack3 = NativeStack(frames: [frame3], modules: [module3]); - final buffer = RingBuffer(3) - ..write(stack1) - ..write(stack2) - ..write(stack3); - - final timestampRange = [now - 10000, now]; - final aggregatedNativeFrames = - SamplerProcessor.aggregateStacks(config, buffer, timestampRange); - expect(aggregatedNativeFrames.length, 2); - expect(aggregatedNativeFrames[0].frame, frame2); - expect(aggregatedNativeFrames[0].occurTimes, 1); - expect(aggregatedNativeFrames[1].frame, frame1); - expect(aggregatedNativeFrames[1].occurTimes, 1); - }); - - test('return filtered modules with kAndroidDefaultModulePathFilters', () { - stackCapturer = FakeStackCapturer(); - final config = SamplerConfig( - jankThreshold: 1, - modulePathFilters: kAndroidDefaultModulePathFilters, - sampleRateInMilliseconds: 1000, - ); - samplerProcessor = SamplerProcessor( - config, - stackCapturer, - ); - final now = Timeline.now; - final module1 = NativeModule( - id: 1, - path: 'libapp.so', - baseAddress: 540641718272, - symbolName: 'hello', - ); - final frame1 = NativeFrame( - pc: 540642472602, - timestamp: now - 5000, - module: module1, - ); - final module2 = NativeModule( - id: 2, - path: 'libflutter.so', - baseAddress: 540641718272, - symbolName: 'world', - ); - final frame2 = NativeFrame( - pc: 540642472608, - timestamp: now - 4000, - module: module2, - ); - final module3 = NativeModule( - id: 3, - path: 'libcore.so', - baseAddress: 540641718272, - symbolName: 'helloworld', - ); - final frame3 = NativeFrame( - pc: 540642472605, - timestamp: now - 3000, - module: module3, - ); - - final stack1 = NativeStack(frames: [frame1], modules: [module1]); - final stack2 = NativeStack(frames: [frame2], modules: [module2]); - final stack3 = NativeStack(frames: [frame3], modules: [module3]); - final buffer = RingBuffer(3) - ..write(stack1) - ..write(stack2) - ..write(stack3); - - final timestampRange = [now - 10000, now]; - final aggregatedNativeFrames = - SamplerProcessor.aggregateStacks(config, buffer, timestampRange); - expect(aggregatedNativeFrames.length, 2); - expect(aggregatedNativeFrames[0].frame, frame2); - expect(aggregatedNativeFrames[0].occurTimes, 1); - expect(aggregatedNativeFrames[1].frame, frame1); - expect(aggregatedNativeFrames[1].occurTimes, 1); - }); - test('only return frames with max length kMaxStackTraces', () { stackCapturer = FakeStackCapturer(); final config = SamplerConfig( jankThreshold: 1, - modulePathFilters: kIOSDefaultModulePathFilters, sampleRateInMilliseconds: 1000, ); samplerProcessor = SamplerProcessor( @@ -955,70 +808,6 @@ void main() { SamplerProcessor.aggregateStacks(config, buffer, timestampRange); expect(aggregatedNativeFrames.length, kMaxStackTraces); }); - - test('return filtered modules with kIOSDefaultModulePathFilters', () { - stackCapturer = FakeStackCapturer(); - final config = SamplerConfig( - jankThreshold: 1, - modulePathFilters: kIOSDefaultModulePathFilters, - sampleRateInMilliseconds: 1000, - ); - samplerProcessor = SamplerProcessor( - config, - stackCapturer, - ); - final now = Timeline.now; - final module1 = NativeModule( - id: 1, - path: 'Runner.app/Frameworks/App.framework/App', - baseAddress: 540641718272, - symbolName: 'hello', - ); - final frame1 = NativeFrame( - pc: 540642472602, - timestamp: now - 5000, - module: module1, - ); - final module2 = NativeModule( - id: 2, - path: 'Runner.app/Frameworks/Flutter.framework/Flutter', - baseAddress: 540641718272, - symbolName: 'world', - ); - final frame2 = NativeFrame( - pc: 540642472608, - timestamp: now - 4000, - module: module2, - ); - final module3 = NativeModule( - id: 3, - path: 'Runner.app/Frameworks/Core.framework/Core', - baseAddress: 540641718272, - symbolName: 'helloworld', - ); - final frame3 = NativeFrame( - pc: 540642472605, - timestamp: now - 3000, - module: module3, - ); - - final stack1 = NativeStack(frames: [frame1], modules: [module1]); - final stack2 = NativeStack(frames: [frame2], modules: [module2]); - final stack3 = NativeStack(frames: [frame3], modules: [module3]); - final buffer = RingBuffer(3) - ..write(stack1) - ..write(stack2) - ..write(stack3); - - final timestampRange = [now - 10000, now]; - final aggregatedNativeFrames = - SamplerProcessor.aggregateStacks(config, buffer, timestampRange); - expect(aggregatedNativeFrames.length, 2); - expect(aggregatedNativeFrames[0].frame, frame2); - expect(aggregatedNativeFrames[0].occurTimes, 1); - expect(aggregatedNativeFrames[1].frame, frame1); - expect(aggregatedNativeFrames[1].occurTimes, 1); - }); }); }); @@ -1065,7 +854,7 @@ void main() { final buffer = RingBuffer(3); buffer.write(1); buffer.write(2); - expect(buffer.readAll(), equals([1, 2])); + expect(buffer.readAllReversed(), equals([2, 1])); }); test('readAll after full', () { @@ -1073,7 +862,7 @@ void main() { buffer.write(1); buffer.write(2); buffer.write(3); - expect(buffer.readAll(), equals([2, 3])); + expect(buffer.readAllReversed(), equals([3, 2])); }); }); }