Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
- Cleanup platform mocking ([#2730](https://github.com/getsentry/sentry-dart/pull/2730))
- The `PlatformChecker` was renamed to `RuntimeChecker`
- Moved `PlatformChecker.platform` to `options.platform`
- Improve platform memory collection on windows/linux ([#2774](https://github.com/getsentry/sentry-dart/pull/2774))
- Fixes an issue where total memory on windows was not read.
- Free memory collection was removed on windows/linux, due to performance impact.

## 8.14.0

### Fixes

This release fixes an issue where Cold starts can be incorrectly reported as Warm starts on Android.

### Behavioral changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
final SentryOptions _options;
late final String _dartVersion = _extractDartVersion(Platform.version);
late final SentryOperatingSystem _os = extractOperatingSystem(
Platform.operatingSystem, Platform.operatingSystemVersion);
Platform.operatingSystem,
Platform.operatingSystemVersion,
);
bool _fetchedTotalPhysicalMemory = false;
int? _totalPhysicalMemory;

/// Extracts the semantic version and channel from the full version string.
///
Expand All @@ -37,17 +41,15 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
}

@override
SentryEvent? apply(SentryEvent event, Hint hint) {
// Amend app with current memory usage, as this is not available on native.
final app = _getApp(event.contexts.app);

final contexts = event.contexts.copyWith(
device: _getDevice(event.contexts.device),
operatingSystem: _getOperatingSystem(event.contexts.operatingSystem),
runtimes: _getRuntimes(event.contexts.runtimes),
app: app,
culture: _getSentryCulture(event.contexts.culture),
);
Future<SentryEvent?> apply(SentryEvent event, Hint hint) async {
final contexts = event.contexts;

contexts.device = await _getDevice(event.contexts.device);
contexts.operatingSystem =
_getOperatingSystem(event.contexts.operatingSystem);
contexts.runtimes = _getRuntimes(event.contexts.runtimes);
contexts.app = _getApp(event.contexts.app);
contexts.culture = _getSentryCulture(event.contexts.culture);

contexts['dart_context'] = _getDartContext();

Expand Down Expand Up @@ -99,17 +101,25 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
};
}

SentryDevice _getDevice(SentryDevice? device) {
final platformMemory = PlatformMemory(_options);
Future<SentryDevice> _getDevice(SentryDevice? device) async {
return (device ?? SentryDevice()).copyWith(
name: device?.name ??
(_options.sendDefaultPii ? Platform.localHostname : null),
processorCount: device?.processorCount ?? Platform.numberOfProcessors,
memorySize: device?.memorySize ?? platformMemory.getTotalPhysicalMemory(),
freeMemory: device?.freeMemory ?? platformMemory.getFreePhysicalMemory(),
memorySize: device?.memorySize ?? await _getTotalPhysicalMemory(),
freeMemory: device?.freeMemory,
);
}

Future<int?> _getTotalPhysicalMemory() async {
if (!_fetchedTotalPhysicalMemory) {
_totalPhysicalMemory =
await PlatformMemory(_options).getTotalPhysicalMemory();
_fetchedTotalPhysicalMemory = true;
}
return _totalPhysicalMemory;
}

SentryApp _getApp(SentryApp? app) {
return (app ?? SentryApp()).copyWith(
appMemory: app?.appMemory ?? ProcessInfo.currentRss,
Expand Down
88 changes: 60 additions & 28 deletions dart/lib/src/event_processor/enricher/io_platform_memory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,47 @@
// Get total & free platform memory (in bytes) for linux and windows operating systems.
// Source: https://github.com/onepub-dev/system_info/blob/8a9bf6b8eb7c86a09b3c3df4bf6d7fa5a6b50732/lib/src/platform/memory.dart
class PlatformMemory {
PlatformMemory(this.options);

final SentryOptions options;

int? getTotalPhysicalMemory() {
if (options.platform.isLinux) {
return _getLinuxMemInfoValue('MemTotal');
} else if (options.platform.isWindows) {
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');
PlatformMemory(this.options) {
if (options.platform.isWindows) {
// Check for WMIC (deprecated in newer Windows versions)
// https://techcommunity.microsoft.com/blog/windows-itpro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/4039242
useWindowsWmci =
File('C:\\Windows\\System32\\wbem\\wmic.exe').existsSync();
if (!useWindowsWmci) {
useWindowsPowerShell = File(

Check warning on line 16 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L13-L16

Added lines #L13 - L16 were not covered by tests
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe')
.existsSync();

Check warning on line 18 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L18

Added line #L18 was not covered by tests
} else {
useWindowsPowerShell = false;

Check warning on line 20 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L20

Added line #L20 was not covered by tests
}
} else {
return null;
useWindowsWmci = false;
useWindowsPowerShell = false;
}
}

int? getFreePhysicalMemory() {
final SentryOptions options;
late final bool useWindowsWmci;
late final bool useWindowsPowerShell;

Future<int?> getTotalPhysicalMemory() async {
if (options.platform.isLinux) {
return _getLinuxMemInfoValue('MemFree');
return _getLinuxMemInfoValue('MemTotal');
} else if (options.platform.isWindows) {
return _getWindowsWmicValue('OS', 'FreePhysicalMemory');
if (useWindowsWmci) {
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');
} else if (useWindowsPowerShell) {
return _getWindowsPowershellTotalMemoryValue();

Check warning on line 39 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L36-L39

Added lines #L36 - L39 were not covered by tests
} else {
return null;
}
} else {
return null;
}
}

int? _getWindowsWmicValue(String section, String key) {
final os = _wmicGetValueAsMap(section, [key]);
Future<int?> _getWindowsWmicValue(String section, String key) async {
final os = await _wmicGetValueAsMap(section, [key]);

Check warning on line 49 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L48-L49

Added lines #L48 - L49 were not covered by tests
final totalPhysicalMemoryValue = os?[key];
if (totalPhysicalMemoryValue == null) {
return null;
Expand All @@ -43,12 +58,10 @@
return size;
}

int? _getLinuxMemInfoValue(String key) {
final meminfoList = _exec('cat', ['/proc/meminfo'])
?.trim()
.replaceAll('\r\n', '\n')
.split('\n') ??
[];
Future<int?> _getLinuxMemInfoValue(String key) async {
final result = await _exec('cat', ['/proc/meminfo']);
final meminfoList =
result?.trim().replaceAll('\r\n', '\n').split('\n') ?? [];

final meminfoMap = _listToMap(meminfoList, ':');
final memsizeResults = meminfoMap[key]?.split(' ') ?? [];
Expand All @@ -65,11 +78,11 @@
return memsize;
}

String? _exec(String executable, List<String> arguments,
{bool runInShell = false}) {
Future<String?> _exec(String executable, List<String> arguments,
{bool runInShell = false}) async {
try {
final result =
Process.runSync(executable, arguments, runInShell: runInShell);
await Process.run(executable, arguments, runInShell: runInShell);
if (result.exitCode == 0) {
return result.stdout.toString();
}
Expand All @@ -82,16 +95,16 @@
return null;
}

Map<String, String>? _wmicGetValueAsMap(String section, List<String> fields) {
Future<Map<String, String>?> _wmicGetValueAsMap(

Check warning on line 98 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L98

Added line #L98 was not covered by tests
String section, List<String> fields) async {
final arguments = <String>[section];
arguments
..add('get')
..addAll(fields.join(', ').split(' '))
..add('/VALUE');

final list =
_exec('wmic', arguments)?.trim().replaceAll('\r\n', '\n').split('\n') ??
[];
final result = await _exec('wmic', arguments);
final list = result?.trim().replaceAll('\r\n', '\n').split('\n') ?? [];

Check warning on line 107 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L106-L107

Added lines #L106 - L107 were not covered by tests

return _listToMap(list, '=');
}
Expand All @@ -108,4 +121,23 @@
}
return map;
}

Future<int?> _getWindowsPowershellTotalMemoryValue() async {

Check warning on line 125 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L125

Added line #L125 was not covered by tests
final command =
'Get-CimInstance Win32_ComputerSystem | Select-Object -ExpandProperty TotalPhysicalMemory';

final result = await _exec('powershell.exe',
['-NoProfile', '-NonInteractive', '-Command', command]);

Check warning on line 130 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L129-L130

Added lines #L129 - L130 were not covered by tests
if (result == null) {
return null;
}

final value = result.trim();
final size = int.tryParse(value);

Check warning on line 136 in dart/lib/src/event_processor/enricher/io_platform_memory.dart

View check run for this annotation

Codecov / codecov/patch

dart/lib/src/event_processor/enricher/io_platform_memory.dart#L135-L136

Added lines #L135 - L136 were not covered by tests
if (size == null) {
return null;
}

return size;
}
}
3 changes: 3 additions & 0 deletions dart/lib/src/protocol/contexts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class Contexts extends MapView<String, dynamic> {
List<SentryRuntime> get runtimes =>
List.unmodifiable(this[SentryRuntime.listType] ?? []);

set runtimes(List<SentryRuntime> runtimes) =>
this[SentryRuntime.listType] = runtimes;

void addRuntime(SentryRuntime runtime) =>
this[SentryRuntime.listType].add(runtime);

Expand Down
2 changes: 1 addition & 1 deletion dart/lib/src/scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class Scope {
level: level ?? event.level);
}

_contexts.clone().forEach((key, value) {
_contexts.forEach((key, value) {
// add the contexts runtime list to the event.contexts.runtimes
if (key == SentryRuntime.listType &&
value is List<SentryRuntime> &&
Expand Down
13 changes: 13 additions & 0 deletions dart/test/contexts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ void main() {
expect(clone['theme'], {'value': 'material'});
expect(clone['version'], {'value': 9});
});

test('set runtimes', () {
final contexts = Contexts();
contexts.runtimes = [
const SentryRuntime(name: 'testRT1', version: '1.0'),
const SentryRuntime(name: 'testRT2', version: '2.0'),
];
expect(contexts.runtimes.length, 2);
expect(contexts.runtimes.first.name, 'testRT1');
expect(contexts.runtimes.first.version, '1.0');
expect(contexts.runtimes.last.name, 'testRT2');
expect(contexts.runtimes.last.version, '2.0');
});
});

group('parse contexts', () {
Expand Down
40 changes: 20 additions & 20 deletions dart/test/event_processor/enricher/io_enricher_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ void main() {
fixture = Fixture();
});

test('adds dart runtime', () {
test('adds dart runtime', () async {
final enricher = fixture.getSut();
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.runtimes, isNotEmpty);
final dartRuntime = event?.contexts.runtimes
Expand All @@ -31,12 +31,12 @@ void main() {
expect(Platform.version, contains(dartRuntime.version.toString()));
});

test('does add to existing runtimes', () {
test('does add to existing runtimes', () async {
final runtime = SentryRuntime(name: 'foo', version: 'bar');
var event = SentryEvent(contexts: Contexts(runtimes: [runtime]));
final enricher = fixture.getSut();

event = enricher.apply(event, Hint())!;
event = (await enricher.apply(event, Hint()))!;

expect(event.contexts.runtimes.contains(runtime), true);
// second runtime is Dart runtime
Expand All @@ -45,10 +45,10 @@ void main() {

group('adds device, os and culture', () {
for (final hasNativeIntegration in [true, false]) {
test('native=$hasNativeIntegration', () {
test('native=$hasNativeIntegration', () async {
final enricher =
fixture.getSut(hasNativeIntegration: hasNativeIntegration);
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.device, isNotNull);
expect(event?.contexts.operatingSystem, isNotNull);
Expand All @@ -57,31 +57,31 @@ void main() {
}
});

test('device has no name if sendDefaultPii = false', () {
test('device has no name if sendDefaultPii = false', () async {
final enricher = fixture.getSut();
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.device?.name, isNull);
});

test('device has name if sendDefaultPii = true', () {
test('device has name if sendDefaultPii = true', () async {
final enricher = fixture.getSut(includePii: true);
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.device?.name, isNotNull);
});

test('culture has locale and timezone', () {
test('culture has locale and timezone', () async {
final enricher = fixture.getSut();
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.culture?.locale, isNotNull);
expect(event?.contexts.culture?.timezone, isNotNull);
});

test('os has name and version', () {
test('os has name and version', () async {
final enricher = fixture.getSut();
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

expect(event?.contexts.operatingSystem?.name, isNotNull);
if (Platform.isLinux) {
Expand Down Expand Up @@ -158,9 +158,9 @@ void main() {
});
});

test('adds Dart context with PII', () {
test('adds Dart context with PII', () async {
final enricher = fixture.getSut(includePii: true);
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

final dartContext = event?.contexts['dart_context'];
expect(dartContext, isNotNull);
Expand All @@ -171,9 +171,9 @@ void main() {
// package_config and executable_arguments are optional
});

test('adds Dart context without PII', () {
test('adds Dart context without PII', () async {
final enricher = fixture.getSut(includePii: false);
final event = enricher.apply(SentryEvent(), Hint());
final event = await enricher.apply(SentryEvent(), Hint());

final dartContext = event?.contexts['dart_context'];
expect(dartContext, isNotNull);
Expand All @@ -185,7 +185,7 @@ void main() {
// and Platform is not mockable
});

test('does not override event', () {
test('does not override event', () async {
final fakeEvent = SentryEvent(
contexts: Contexts(
device: SentryDevice(
Expand All @@ -207,7 +207,7 @@ void main() {
hasNativeIntegration: false,
);

final event = enricher.apply(fakeEvent, Hint());
final event = await enricher.apply(fakeEvent, Hint());

// contexts.device
expect(
Expand Down
Loading
Loading