Skip to content

Add memory usage to contexts #2133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jul 9, 2024
Merged
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 @@ -2,6 +2,11 @@

## Unreleased

### Features

- Add memory usage to contexts ([#2133](https://github.com/getsentry/sentry-dart/pull/2133))
- Only for Linux/Windows applications, as iOS/Android/macOS use native SDKs

### Fixes

- App starts hanging for 30s ([#2140](https://github.com/getsentry/sentry-dart/pull/2140))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:io';

import '../../../sentry.dart';
import 'enricher_event_processor.dart';
import 'io_platform_memory.dart';

EnricherEventProcessor enricherEventProcessor(SentryOptions options) {
return IoEnricherEventProcessor(options);
Expand All @@ -17,25 +18,29 @@ 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);

// If there's a native integration available, it probably has better
// information available than Flutter.

final os = _options.platformChecker.hasNativeIntegration
? null
: _getOperatingSystem(event.contexts.operatingSystem);

final device = _options.platformChecker.hasNativeIntegration
? null
: _getDevice(event.contexts.device);

final os = _options.platformChecker.hasNativeIntegration
? null
: _getOperatingSystem(event.contexts.operatingSystem);

final culture = _options.platformChecker.hasNativeIntegration
? null
: _getSentryCulture(event.contexts.culture);

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

Expand Down Expand Up @@ -97,9 +102,18 @@ class IoEnricherEventProcessor implements EnricherEventProcessor {
}

SentryDevice _getDevice(SentryDevice? device) {
final platformMemory = PlatformMemory(_options);
return (device ?? SentryDevice()).copyWith(
name: device?.name ?? Platform.localHostname,
processorCount: device?.processorCount ?? Platform.numberOfProcessors,
memorySize: device?.memorySize ?? platformMemory.getTotalPhysicalMemory(),
freeMemory: device?.freeMemory ?? platformMemory.getFreePhysicalMemory(),
);
}

SentryApp _getApp(SentryApp? app) {
return (app ?? SentryApp()).copyWith(
appMemory: app?.appMemory ?? ProcessInfo.currentRss,
);
}

Expand Down
108 changes: 108 additions & 0 deletions dart/lib/src/event_processor/enricher/io_platform_memory.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'dart:io';

import '../../protocol.dart';
import '../../sentry_options.dart';

// 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.platformChecker.platform.isLinux) {
return _getLinuxMemInfoValue('MemTotal');
} else if (options.platformChecker.platform.isWindows) {
return _getWindowsWmicValue('ComputerSystem', 'TotalPhysicalMemory');

Check warning on line 17 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#L16-L17

Added lines #L16 - L17 were not covered by tests
} else {
return null;
}
}

int? getFreePhysicalMemory() {
if (options.platformChecker.platform.isLinux) {
return _getLinuxMemInfoValue('MemFree');
} else if (options.platformChecker.platform.isWindows) {
return _getWindowsWmicValue('OS', 'FreePhysicalMemory');

Check warning on line 27 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#L26-L27

Added lines #L26 - L27 were not covered by tests
} else {
return null;
}
}

int? _getWindowsWmicValue(String section, String key) {
final os = _wmicGetValueAsMap(section, [key]);
final totalPhysicalMemoryValue = os?[key];

Check warning on line 35 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#L33-L35

Added lines #L33 - L35 were not covered by tests
if (totalPhysicalMemoryValue == null) {
return null;
}
final size = int.tryParse(totalPhysicalMemoryValue);

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#L39

Added line #L39 was not covered by tests
if (size == null) {
return null;
}
return size;
}

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

Check warning on line 51 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#L51

Added line #L51 was not covered by tests

final meminfoMap = _listToMap(meminfoList, ':');
final memsizeResults = meminfoMap[key]?.split(' ') ?? [];

if (memsizeResults.isEmpty) {
return null;
}
final memsizeResult = memsizeResults.first;

final memsize = int.tryParse(memsizeResult);
if (memsize == null) {
return null;
}
return memsize;
}

String? _exec(String executable, List<String> arguments,
{bool runInShell = false}) {
try {
final result =
Process.runSync(executable, arguments, runInShell: runInShell);
if (result.exitCode == 0) {
return result.stdout.toString();
}
} catch (e) {
options.logger(SentryLevel.warning, "Failed to run process: $e");

Check warning on line 77 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#L77

Added line #L77 was not covered by tests
}
return null;
}

Map<String, String>? _wmicGetValueAsMap(String section, List<String> fields) {
final arguments = <String>[section];

Check warning on line 83 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#L82-L83

Added lines #L82 - L83 were not covered by tests
arguments
..add('get')
..addAll(fields.join(', ').split(' '))
..add('/VALUE');

Check warning on line 87 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#L85-L87

Added lines #L85 - L87 were not covered by tests

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

Check warning on line 91 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#L90-L91

Added lines #L90 - L91 were not covered by tests

return _listToMap(list, '=');

Check warning on line 93 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#L93

Added line #L93 was not covered by tests
}

Map<String, String> _listToMap(List<String> list, String separator) {
final map = <String, String>{};
for (final string in list) {
final index = string.indexOf(separator);
if (index != -1) {
final key = string.substring(0, index).trim();
final value = string.substring(index + 1).trim();
map[key] = value;
}
}
return map;
}
}
60 changes: 60 additions & 0 deletions dart/test/event_processor/enricher/io_platform_memory_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@TestOn('vm')
library dart_test;

import 'dart:io';

import 'package:sentry/sentry.dart';
import 'package:sentry/src/event_processor/enricher/io_platform_memory.dart';
import 'package:test/test.dart';

void main() {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

test('total physical memory', () {
final sut = fixture.getSut();
final totalPhysicalMemory = sut.getTotalPhysicalMemory();

switch (Platform.operatingSystem) {
case 'linux':
expect(totalPhysicalMemory, isNotNull);
expect(totalPhysicalMemory! > 0, true);
break;
case 'windows':
expect(totalPhysicalMemory, isNotNull);
expect(totalPhysicalMemory! > 0, true);
break;
default:
expect(totalPhysicalMemory, isNull);
}
});

test('free physical memory', () {
final sut = fixture.getSut();
final freePhysicalMemory = sut.getTotalPhysicalMemory();

switch (Platform.operatingSystem) {
case 'linux':
expect(freePhysicalMemory, isNotNull);
expect(freePhysicalMemory! > 0, true);
break;
case 'windows':
expect(freePhysicalMemory, isNotNull);
expect(freePhysicalMemory! > 0, true);
break;
default:
expect(freePhysicalMemory, isNull);
}
});
}

class Fixture {
var options = SentryOptions();

PlatformMemory getSut() {
return PlatformMemory(options);
}
}
Loading