Skip to content

Commit

Permalink
Add memory usage to contexts (#2133)
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase authored Jul 9, 2024
1 parent f172c4d commit 9f9dd52
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 5 deletions.
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');
} else {
return null;
}
}

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

int? _getWindowsWmicValue(String section, String key) {
final os = _wmicGetValueAsMap(section, [key]);
final totalPhysicalMemoryValue = os?[key];
if (totalPhysicalMemoryValue == null) {
return null;
}
final size = int.tryParse(totalPhysicalMemoryValue);
if (size == null) {
return null;
}
return size;
}

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

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");
}
return null;
}

Map<String, String>? _wmicGetValueAsMap(String section, List<String> fields) {
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') ??
[];

return _listToMap(list, '=');
}

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);
}
}

0 comments on commit 9f9dd52

Please sign in to comment.