Skip to content
Closed
Show file tree
Hide file tree
Changes from 12 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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
# Changelog

## Unreleased

- Improve platform memory collection on windows/linux ([#2774](https://github.com/getsentry/sentry-dart/pull/2774))
- Fixes an issue where total/free memory on windows was not read.
- Memory collection is disabled per default on windows/linux, due to performance impact.
- Can be enabled `SentryOptions.collectPlatformDeviceMemory`
- If enabled, free system memory on windows/linux is now cached for 1 minute.

## 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 @@ -15,12 +15,19 @@
/// Uses Darts [Platform](https://api.dart.dev/stable/dart-io/Platform-class.html)
/// class to read information.
class IoEnricherEventProcessor implements EnricherEventProcessor {
IoEnricherEventProcessor(this._options);
IoEnricherEventProcessor(this._options) {
_platformMemory = _options.collectPlatformDeviceMemory
? CachedPlatformMemory(_options)

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L20 was not covered by tests
: null;
}

final SentryOptions _options;
late final String _dartVersion = _extractDartVersion(Platform.version);
late final SentryOperatingSystem _os = extractOperatingSystem(
Platform.operatingSystem, Platform.operatingSystemVersion);
Platform.operatingSystem,
Platform.operatingSystemVersion,
);
late final CachedPlatformMemory? _platformMemory;

/// Extracts the semantic version and channel from the full version string.
///
Expand All @@ -38,16 +45,14 @@

@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),
);
final contexts = event.contexts;

contexts.device = _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 @@ -114,13 +119,14 @@
}

SentryDevice _getDevice(SentryDevice? device) {
final platformMemory = PlatformMemory(_options);
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 ?? _platformMemory?.getTotalPhysicalMemory(),
freeMemory:
device?.freeMemory ?? _platformMemory?.getFreePhysicalMemory(),
);
}

Expand Down
95 changes: 92 additions & 3 deletions dart/lib/src/event_processor/enricher/io_platform_memory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,40 @@
// 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);
PlatformMemory(this.options) {
if (options.platformChecker.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 {
useWindowsWmci = false;
useWindowsPowerShell = false;
}
}

final SentryOptions options;
late final bool useWindowsWmci;
late final bool useWindowsPowerShell;

int? getTotalPhysicalMemory() {
if (options.platformChecker.platform.isLinux) {
return _getLinuxMemInfoValue('MemTotal');
} else if (options.platformChecker.platform.isWindows) {
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');
if (useWindowsWmci) {
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');
} else if (useWindowsPowerShell) {
return _getWindowsPowershellMemoryValue('TotalPhysicalMemory');

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;
}
Expand All @@ -24,7 +49,13 @@
if (options.platformChecker.platform.isLinux) {
return _getLinuxMemInfoValue('MemFree');
} else if (options.platformChecker.platform.isWindows) {
return _getWindowsWmicValue('OS', 'FreePhysicalMemory');
if (useWindowsWmci) {
return _getWindowsWmicValue('OS', 'FreePhysicalMemory');
} else if (useWindowsPowerShell) {
return _getWindowsPowershellMemoryValue('FreePhysicalMemory');

Check warning on line 55 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#L52-L55

Added lines #L52 - L55 were not covered by tests
} else {
return null;
}
} else {
return null;
}
Expand Down Expand Up @@ -108,4 +139,62 @@
}
return map;
}

int? _getWindowsPowershellMemoryValue(String property) {
final command = property == 'TotalPhysicalMemory'

Check warning on line 144 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#L143-L144

Added lines #L143 - L144 were not covered by tests
? 'Get-CimInstance Win32_ComputerSystem | Select-Object -ExpandProperty TotalPhysicalMemory'
: 'Get-CimInstance Win32_OperatingSystem | Select-Object -ExpandProperty FreePhysicalMemory';

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

Check warning on line 149 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#L148-L149

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

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

Check warning on line 155 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#L154-L155

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

// FreePhysicalMemory is in KB, while TotalPhysicalMemory is in bytes
return property == 'TotalPhysicalMemory' ? size : size * 1024;

Check warning on line 161 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#L161

Added line #L161 was not covered by tests
}
}

/// A cached version of [PlatformMemory] that reduces system calls by caching
/// values. Total memory is cached indefinitely, and free memory for the
/// configured duration (default 1 minute).
class CachedPlatformMemory {
CachedPlatformMemory(SentryOptions options, {Duration? cacheDuration})

Check warning on line 169 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#L169

Added line #L169 was not covered by tests
: _cacheDuration = cacheDuration ?? const Duration(minutes: 1) {
_delegate = PlatformMemory(options);

Check warning on line 171 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#L171

Added line #L171 was not covered by tests
}

final Duration _cacheDuration;
late final PlatformMemory _delegate;

int? _cachedTotalPhysicalMemory;
int? _cachedFreePhysicalMemory;
DateTime? _lastCacheUpdate;

void _refreshCachedFreePhysicalMemory() {
final now = DateTime.now();
if (_lastCacheUpdate != null &&
now.difference(_lastCacheUpdate!) < _cacheDuration) {

Check warning on line 184 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#L181-L184

Added lines #L181 - L184 were not covered by tests
return;
}
_cachedFreePhysicalMemory = _delegate.getFreePhysicalMemory();
_lastCacheUpdate = now;

Check warning on line 188 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#L187-L188

Added lines #L187 - L188 were not covered by tests
}

int? getTotalPhysicalMemory() {
_cachedTotalPhysicalMemory ??= _delegate.getTotalPhysicalMemory();
return _cachedTotalPhysicalMemory;

Check warning on line 193 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#L191-L193

Added lines #L191 - L193 were not covered by tests
}

int? getFreePhysicalMemory() {
_refreshCachedFreePhysicalMemory();
return _cachedFreePhysicalMemory;

Check warning on line 198 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#L196-L198

Added lines #L196 - L198 were not covered by tests
}
}
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
4 changes: 4 additions & 0 deletions dart/lib/src/sentry_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,10 @@ class SentryOptions {
/// If enabled, [scopeObservers] will be called when mutating scope.
bool enableScopeSync = true;

/// Read memory information on Windows & Linux (total/free) by calling system APIs.
/// This might have negative performance impact, use with caution.
bool collectPlatformDeviceMemory = false;

final List<ScopeObserver> _scopeObservers = [];

List<ScopeObserver> get scopeObservers => _scopeObservers;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,19 @@ class FlutterEnricherEventProcessor implements EventProcessor {
SentryEvent event,
Hint hint,
) async {
final contexts = event.contexts;

// If there's a native integration available, it probably has better
// information available than Flutter.
// TODO: while we have a native integration with JS SDK, it's currently opt in and we dont gather contexts yet
// so for web it's still better to rely on the information of Flutter.
final device = _hasNativeIntegration && !_checker.isWeb
contexts.device = _hasNativeIntegration && !_checker.isWeb
? null
: _getDevice(event.contexts.device);

final contexts = event.contexts.copyWith(
device: device,
runtimes: _getRuntimes(event.contexts.runtimes),
culture: _getCulture(event.contexts.culture),
operatingSystem: _getOperatingSystem(event.contexts.operatingSystem),
app: _getApp(event.contexts.app),
);
contexts.runtimes = _getRuntimes(contexts.runtimes);
contexts.culture = _getCulture(contexts.culture);
contexts.operatingSystem = _getOperatingSystem(contexts.operatingSystem);
contexts.app = _getApp(contexts.app);

final app = contexts.app;
if (app != null) {
Expand Down
Loading