chaos_monkey_dart 1.0.0 copy "chaos_monkey_dart: ^1.0.0" to clipboard
chaos_monkey_dart: ^1.0.0 copied to clipboard

A Flutter/Dart chaos engineering package that injects controlled failures (network delays, crashes, memory/CPU stress, data corruption) to test app resilience in staging environments.

πŸ’ chaos_monkey_dart #

pub version License: MIT Dart SDK

"If you don't break it first, production will β€” at the worst possible moment."

A production-grade chaos engineering package for Flutter & Dart.
Deliberately injects network delays, database failures, file corruption, memory
pressure, CPU spikes, and random exceptions to surface resilience weaknesses
before real users encounter them.

Inspired by Netflix Chaos Monkey (2011).
Staging & testing only. Never use in production.


Table of Contents #


What is Chaos Engineering? #

Netflix invented Chaos Engineering in 2011 to validate that their distributed systems could survive unexpected failures. The core idea:

Deliberately inject failures in a controlled environment so you find weaknesses before your users do.

chaos_monkey_dart brings this battle-tested practice to Flutter & Dart apps.


Features #

Feature Description
🌐 Network chaos Delay (with jitter) or drop HTTP requests
πŸ—„οΈ Database chaos Kill connections, slow queries, corrupt reads
πŸ“ File chaos Delete or corrupt files matching glob patterns
🧠 Memory chaos Simulate heap pressure with large allocations
πŸ’» CPU chaos Tight-loop spikes that freeze the event loop
πŸ’₯ Exception chaos Randomly throw from a configurable exception pool
⏱️ Latency chaos Generic latency injection for any async call
πŸ›‘οΈ Production guard Refuses to run in kReleaseMode by default
πŸ“Š Rich reporting Console, file (JSON Lines), callback, collector
⏰ Scheduler Fixed-interval and random-interval autonomous firing
πŸŽ›οΈ Presets light, medium, heavy, nuclear out of the box
πŸ” Reproducible Optional RNG seed for identical test replays

Quick Start #

import 'package:flutter/foundation.dart';
import 'package:chaos_monkey_dart/chaos_monkey_dart.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  if (kDebugMode) {
    await ChaosMonkey.start(
      config: ChaosConfig(
        killDatabase:   0.05,    // 5%  β€” DB connection dies
        slowNetwork:    0.20,    // 20% β€” request delayed 10s
        networkDelayMs: 10000,
        dropNetwork:    0.03,    // 3%  β€” request dropped entirely
        throwRandomException: 0.02,
      ),
      isRelease: kReleaseMode,
    );
  }

  runApp(const MyApp());
}

Presets #

// Gentle β€” safe for daily CI pipelines
await ChaosMonkey.start(config: ChaosConfig.light());

// Moderate β€” weekly resilience gate checks
await ChaosMonkey.start(config: ChaosConfig.medium());

// Aggressive β€” dedicated resilience sprints
await ChaosMonkey.start(config: ChaosConfig.heavy());

// Apocalyptic β€” "Chaos Day" events only πŸ”₯
await ChaosMonkey.start(config: ChaosConfig.nuclear());
Preset Intensity Network DB Exceptions
light ~3% 5% slow 1% kill 1%
medium ~9% 15% slow, 3% drop 5% kill, 5% slow 3%
heavy ~18% 30% slow, 10% drop 10% kill 8%
nuclear ~45% 60% slow, 30% drop 25% kill 20%

Experiments #

NetworkChaos β€” HTTP delay & drop #

// Automatic (via Dio interceptor β€” see Interceptors section below)

// Manual wrapping:
final chaos = NetworkChaos(config: config);
try {
  final result = await chaos.applyTo(
    () => http.get(Uri.parse('https://api.example.com/users')),
    url: 'https://api.example.com/users',
    method: 'GET',
  );
} on NetworkDropException catch (e) {
  showErrorBanner('Network unavailable [${e.statusCode}]');
}

DatabaseChaos β€” kill / slow / corrupt #

final dbChaos = DatabaseChaos(config: config);

Future<User?> getUser(int id) async {
  try {
    return await dbChaos.wrap(
      () => db.findUser(id),
      label: 'UserDao.findUser',
    );
  } on DatabaseKillException {
    return _cache.get(id); // fallback to local cache
  }
}

LatencyChaos β€” any async call #

final latency = LatencyChaos(config: config);

// Works on BLE, SQLite, file I/O β€” anything async:
final data = await latency.wrap(
  () => bleDevice.readCharacteristic(),
  label: 'BLE.readCharacteristic',
);

ExceptionChaos β€” random exceptions #

final exChaos = ExceptionChaos(
  config: ChaosConfig(
    throwRandomException: 0.05,
    customExceptions: [
      AuthException('token expired'),
      RateLimitException('429 too many requests'),
    ],
  ),
);

final profile = await exChaos.wrap(
  () => userService.getProfile(userId),
  label: 'UserService.getProfile',
);

Interceptors #

Dio #

// Copy ChaosMonkeyDioInterceptor from example/lib/example_with_dio.dart
// (requires dio: ^5.4.0 in your pubspec)

final dio = Dio();
if (!kReleaseMode) {
  dio.interceptors.add(
    ChaosMonkeyDioInterceptor(
      config: ChaosConfig(
        slowNetwork: 0.20,
        networkDelayMs: 10000,
        dropNetwork: 0.05,
      ),
    ),
  );
}

http package #

// Copy ChaosAwareHttpClient from example/lib/example_with_http.dart
// (requires http: ^1.2.0 in your pubspec)

final client = ChaosAwareHttpClient(
  config: ChaosConfig(slowNetwork: 0.20, dropNetwork: 0.03),
);
final response = await client.get(Uri.parse('https://api.example.com'));

Reporters #

// Console (default) β€” coloured ASCII art
await ChaosMonkey.start(config: config, reporter: ConsoleReporter());

// File β€” JSON Lines
await ChaosMonkey.start(
  config: config,
  reporter: FileReporter(logPath: '/tmp/chaos_session.json'),
);

// Callback β€” custom observability pipeline
await ChaosMonkey.start(
  config: config,
  reporter: CallbackReporter(
    onEvent: (e) => analytics.track('chaos_event', e.metadata),
    onStopped: (r) => print('Session: ${r.totalEventsTriggered} events'),
  ),
);

// Collector β€” for test assertions
final collector = EventCollectorReporter();
await ChaosMonkey.start(config: config, reporter: collector);
// ... run test
await ChaosMonkey.stop();
expect(collector.eventsFor('NetworkChaos'), hasLength(greaterThan(0)));

// Multi β€” fan out to several reporters
await ChaosMonkey.start(
  config: config,
  reporter: MultiReporter([ConsoleReporter(), collector]),
);

Scheduler #

// Fixed interval (default β€” fires every 60s)
ChaosConfig(schedulerIntervalSeconds: 60)

// Random interval (more realistic)
final scheduler = RandomScheduler(
  experiments: myExperiments,
  minIntervalSeconds: 10,
  maxIntervalSeconds: 90,
  onEvent: handleEvent,
  isPausedCallback: () => ChaosMonkey.status().isPaused,
);

API Reference #

ChaosMonkey static methods #

Method Description
start({config, reporter, isRelease}) Starts chaos with the given config
quickStart({killDatabase, slowNetwork, ...}) Convenience named-param start
stop() Stops all experiments, returns ChaosReport
pause() Suspends chaos without stopping the scheduler
resume() Resumes after pause
status() Returns lightweight ChaosStatus snapshot
updateConfig(newConfig) Hot-swaps config while running
reset() Resets singleton β€” tests only

Key config parameters #

Parameter Default Description
slowNetwork 0.0 Probability of request delay
networkDelayMs 5000 Delay magnitude in ms
dropNetwork 0.0 Probability of request drop
killDatabase 0.0 Probability of DB connection kill
slowDatabase 0.0 Probability of slow query
throwRandomException 0.0 Probability of random exception
injectLatency 0.0 Probability of generic latency
enabled true Master on/off switch
safetyGuard true Block production environments
seed null RNG seed for reproducibility

Full parameter table: doc/configuration_guide.md


Testing Utilities #

import 'test/helpers/mock_helpers.dart';

// Always-trigger configs for deterministic tests:
final chaos = DatabaseChaos(config: alwaysKillDatabaseConfig());
expect(
  () => chaos.wrap(() async => 'result'),
  throwsA(isA<DatabaseKillException>()),
);

// Collect events during a test:
final events = await withChaosCollector(
  config: ChaosConfig(throwRandomException: 1.0, safetyGuard: false),
  body: () async => myService.doWork(),
);
expect(events.where((e) => e.experimentType == 'ExceptionChaos'), isNotEmpty);

Safety #

chaos_monkey_dart refuses to run in production by default.

// βœ… Safe β€” guard is active, release mode detected β†’ throws
await ChaosMonkey.start(
  config: ChaosConfig(killDatabase: 0.05),
  isRelease: kReleaseMode, // kReleaseMode = true β†’ ChaosInProductionException
);

// βœ… Safe β€” wrapped with debug check
if (kDebugMode) {
  await ChaosMonkey.start(config: config);
}

// ⚠️ Override β€” only if you have a custom staging-in-release setup
EnvironmentGuard.setCustomProductionCheck(() => myEnv.isProduction);

Full safety guide: doc/safety_guide.md


Architecture #

ChaosMonkey (controller)
    β”‚
    β”œβ”€β”€ ChaosConfig (immutable settings)
    β”‚
    β”œβ”€β”€ Experiments
    β”‚   β”œβ”€β”€ NetworkChaos    ← HTTP delay/drop
    β”‚   β”œβ”€β”€ DatabaseChaos   ← DB kill/slow/corrupt
    β”‚   β”œβ”€β”€ FileChaos       ← File delete/corrupt
    β”‚   β”œβ”€β”€ MemoryChaos     ← Heap pressure
    β”‚   β”œβ”€β”€ CpuChaos        ← CPU spike
    β”‚   β”œβ”€β”€ ExceptionChaos  ← Random throws
    β”‚   └── LatencyChaos    ← Generic async delay
    β”‚
    β”œβ”€β”€ Interceptors
    β”‚   β”œβ”€β”€ ChaosDioInterceptor   ← Dio pipeline
    β”‚   β”œβ”€β”€ ChaosHttpInterceptor  ← http package
    β”‚   └── ChaosHttpClient       ← Drop-in http.BaseClient
    β”‚
    β”œβ”€β”€ Reporters
    β”‚   β”œβ”€β”€ ConsoleReporter       ← ANSI console
    β”‚   β”œβ”€β”€ FileReporter          ← JSON Lines / text
    β”‚   β”œβ”€β”€ CallbackReporter      ← User hooks
    β”‚   β”œβ”€β”€ EventCollectorReporter← In-memory (tests)
    β”‚   β”œβ”€β”€ MultiReporter         ← Fan-out
    β”‚   └── SilentReporter        ← No-op
    β”‚
    β”œβ”€β”€ Scheduler
    β”‚   β”œβ”€β”€ ChaosScheduler        ← Fixed interval
    β”‚   └── RandomScheduler       ← Random interval
    β”‚
    └── Utils
        β”œβ”€β”€ Probability           ← roll / statistics
        β”œβ”€β”€ EnvironmentGuard      ← Production detection
        └── ChaosLogger           ← Structured logging

FAQ #

Q: Will this affect my release build?
A: No. With safetyGuard: true (default), the package throws if it detects kReleaseMode = true. Wrap with if (kDebugMode) for belt-and-braces safety.

Q: Can I use this with Riverpod / Bloc / GetX?
A: Yes β€” ChaosMonkey is framework-agnostic. Wrap your repository methods with the experiment classes regardless of state management choice.

Q: Does it work on web?
A: Network and exception chaos work everywhere. File, memory, and CPU chaos use dart:io and require native platforms.

Q: How do I get reproducible chaos in CI?
A: Pass seed: 42 (or any fixed integer) to ChaosConfig. Every call with the same seed produces identical outcomes.

Q: How do I disable chaos without removing code?
A: Set enabled: false in ChaosConfig. All experiments become silent no-ops.


License #

MIT β€” see LICENSE.


"chaos_monkey_dart doesn't break your app β€” it reveals what was already broken."

0
likes
140
points
36
downloads
screenshot

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter/Dart chaos engineering package that injects controlled failures (network delays, crashes, memory/CPU stress, data corruption) to test app resilience in staging environments.

Repository (GitHub)
View/report issues

Topics

#testing #chaos-engineering #fault-injection #resilience #staging

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

logging, meta

More

Packages that depend on chaos_monkey_dart