Architecture
This guide explains how PureEdgeSim is organized internally: the modules, the objects that own the simulation state, the initialization sequence, and the event engine that advances time. Read it before modifying the framework or debugging unexpected behavior. If you only want to run experiments, Getting started and Configuration are enough.
Design principles
PureEdgeSim is built around four ideas:
- Separation of concerns. Each module owns one aspect of the simulated system: topology, computing resources, mobility, networking, energy, task generation, orchestration, logging, and visualization.
- Defaults plus extension points. Every module has a working default implementation. Researchers replace only the model their contribution concerns, usually by subclassing a default class and registering the subclass with
Simulation. - No modification of the framework. Custom behavior is added by subclassing, not by editing PureEdgeSim. This keeps experiments compatible with bug fixes and performance improvements.
- Scenario-driven experimentation. A simulation file describes which architectures, algorithms, and device counts to evaluate. The framework expands that specification into independent runs and writes one result row per run.
Package map
The framework source tree is under PureEdgeSim/main/java/com/mechalikh/pureedgesim/, and the runnable scenarios are in PureEdgeSim/examples/.
| Package | Responsibility | Key classes |
|---|---|---|
simulationengine |
Discrete-event engine, event queue, simulation entities, lifecycle listeners | PureEdgeSim, SimEntity, Event, FutureQueue, Queue, OnSimulationStartListener, OnSimulationEndListener |
simulationmanager |
Simulation orchestration, scenario expansion, logging, event handling, charts from CSV | Simulation, SimulationAbstract, SimulationThread, SimulationManager, DefaultSimulationManager, SimLog, ChartsGenerator |
scenariomanager |
Configuration parsing and global parameters | SimulationParameters, ParametersParser, ApplicationFileParser, DatacentersParser, EdgeDevicesParser, ComputingNodesParser, DistributionReader, Scenario |
distribution |
Probability distributions and the shared random source | Distribution, DistributionFactory, Randomness, BoundedDistribution, ScaledDistribution, and one class per distribution |
datacentersmanager |
Computing nodes, resource generation, topology creation | ComputingNode, DefaultComputingNode, ComputingNodesGenerator, DefaultComputingNodesGenerator, DataCentersManager, TopologyCreator, DefaultTopologyCreator |
network |
Links, bandwidth allocation, routing, transfer progress | NetworkModel, DefaultNetworkModel, NetworkLink, InfrastructureGraph, TransferProgress |
energy |
Energy consumption models for nodes and links | EnergyModelComputingNode, EnergyModelNetworkLink |
locationmanager |
Position and mobility | Location, MobilityModel, MobilityModelFactory, DefaultMobilityModel, and the eight additional movement models |
taskgenerator |
Task model and workload generation | Task, TaskAbstract, DefaultTask, TaskGenerator, DefaultTaskGenerator, Application |
taskorchestrator |
Offloading decisions | Orchestrator, DefaultOrchestrator |
simulationvisualizer |
Live charts during a run | SimulationVisualizer, Chart, MapChart, CPUChart, TasksChart, WanChart |
The examples package is deliberately outside the core packages. It demonstrates how to instantiate and extend the framework, and it is excluded from the published Javadoc.
The object graph
At runtime, one simulation iteration is a graph of objects:
flowchart TD
Simulation["Simulation<br/>owns configuration paths and custom class choices"]
Simulation --> Thread["SimulationThread<br/>one per scenario, controls one full iteration"]
Thread --> Engine["PureEdgeSim<br/>event engine and clock"]
Thread --> Log["SimLog<br/>per-iteration metrics and output files"]
Thread --> Manager["SimulationManager<br/>receives engine events, drives the task lifecycle"]
Thread --> Entities["SimEntity instances<br/>all scheduler-driven participants"]
Manager --> Centers["DataCentersManager"]
Manager --> Orchestrator["Orchestrator<br/>chooses execution destinations"]
Manager --> Generator["TaskGenerator<br/>creates the task list"]
Manager --> Network["NetworkModel<br/>transfers requests and results"]
Manager --> Visualizer["SimulationVisualizer<br/>optional, live charts only"]
Centers --> Nodes["ComputingNodesGenerator<br/>creates cloud, edge, and device nodes"]
Centers --> Topology["TopologyCreator<br/>wires links and shortest paths"]
The important ownership rule is that SimulationManager is the hub. It knows the data centers manager, the orchestrator, the task generator output, the network model, the logger, and the engine. Custom models receive the manager in their constructor and access everything through it.
Initialization sequence
The sequence below describes one call to new Simulation().launchSimulation().
1. Parse and validate configuration
Simulation.launchSimulation() calls checkFiles(), which runs five parsers:
ParametersParserreadssimulation_parameters.properties.ApplicationFileParserreadsapplications.xml.DatacentersParserreadscloud.xmlandedge_datacenters.xml.EdgeDevicesParserreadsedge_devices.xml.ComputingNodesParservalidates cross-file consistency, for example that the device percentages and orchestrator counts are coherent.
Every parser validates range constraints and throws IllegalArgumentException with a message that names the offending parameter and file. Parsing also performs unit conversions, which are documented in Configuration.
2. Expand the scenario sweep
loadScenarios() builds the Cartesian product of:
- the architectures in
orchestration_architectures, - the algorithms in
orchestration_algorithms, - the device counts from
min_number_of_edge_devicestomax_number_of_edge_devicesin steps ofedge_device_counter_size.
Each combination becomes a Scenario object that stores the architecture name, algorithm name, and device count. The order is deterministic: architectures outer, algorithms middle, device counts inner.
3. Run each scenario
If parallel_simulation=false, the scenarios run sequentially in the current thread. If parallel_simulation=true, PureEdgeSim uses a parallel stream with a pool sized to min(availableProcessors, scenarioCount). Each worker executes new SimulationThread(simulation, simulationId, iteration).startSimulation().
Parallelism changes two things:
- Live charts are disabled, because a single Swing window cannot represent multiple concurrent simulations.
- Output files are named
Parallel_simulation_<id>.csvinstead ofSequential_simulation.csv.
Each iteration is otherwise independent. Results are written by that iteration’s SimLog.
4. Create the engine and the manager
SimulationThread.startSimulation() performs these steps for one scenario:
- Create a
SimLogfor the iteration. If this is the first iteration andclear_output_folder=true, previous output is deleted. - Instantiate a fresh
PureEdgeSimengine withSimulationParameters.simulationDuration. - Reflectively instantiate the configured
SimulationManagersubclass using the constructor(SimLog, PureEdgeSim, int simulationId, int iteration, Scenario). - Call
loadModels(simulationManager). - Call
simulationManager.startSimulation(). - Pause for
pause_lengthseconds (a visual aid between iterations). - Generate summary charts from the CSV if
save_charts=trueand the run was sequential.
5. Load the models
SimulationThread.loadModels() instantiates the models in dependency order:
- Network model. Instantiated reflectively with a
SimulationManagerconstructor. The constructor creates theInfrastructureGraphand registers link types. - Data centers manager.
new DataCentersManager(manager, mobilityClass, computingNodeClass, generatorClass, topologyClass). It creates the computing nodes generator, generates all nodes, creates the topology, and connects the result to the network model. - Task generator. Instantiated reflectively with a
SimulationManagerconstructor, thengenerate()is called. The resultingFutureQueue<Task>is handed to the manager. - Orchestrator. Instantiated reflectively with a
SimulationManagerconstructor. Its constructor callsinitialize(), which selects the node list that matches the architecture, and registers itself with the manager.
All model classes must therefore expose a public constructor with the exact signature expected by the loader. This is the most common cause of NoSuchMethodException when plugging in a custom model. See Customizing models.
6. Start the event loop
SimulationManager.startSimulation() optionally creates the live visualizer, prints the scenario header, and calls PureEdgeSim.start().
PureEdgeSim.start():
- Sets the clock to 0 and marks the engine as running.
- Calls
onSimulationStart()on every registeredSimEntityand on the simulation manager. - Repeatedly removes the earliest event from the
FutureQueue, advances the clock to its time, and callsprocessEvent(event)on the target entity. - Stops when the queue is empty or
terminate()has been called. - Calls
onSimulationEnd()on listeners. - Clears the entity list so the next iteration starts clean.
The event engine
Time and ordering
Simulation time is a double measured in seconds. The queue orders events by time and, for equal times, by insertion serial number. This gives deterministic ordering when a scenario is run sequentially, which is essential for reproducible experiments.
schedule(SimEntity destination, double delay, int tag, Object data)schedules an eventdelayseconds in the future.scheduleNow(...)is a shorthand for a zero-delay event.clock()returns the current simulation time in seconds.clockInMinutes()returns it in minutes.
Events are not executed immediately when scheduled, even with zero delay. They are executed when the engine reaches them in the queue. This is why a zero-delay event can still allow other events at the same timestamp to run first, depending on their serial numbers.
Simulation entities
A SimEntity is any object that reacts to events. The base class provides schedule, scheduleNow, processEvent, and lifecycle callbacks. Every model that needs timed behavior extends SimEntity and registers itself with the engine, usually by calling super(simulation) in its constructor.
Event tags
DefaultSimulationManager uses the following tags. Custom simulation managers can define additional tags.
| Constant | Value | Purpose |
|---|---|---|
PRINT_LOG |
1 | End of the simulation or periodic progress check; triggers final aggregation and termination. |
SHOW_PROGRESS |
2 | Updates the progress bar during execution. |
EXECUTE_TASK |
3 | The destination received a task and should submit it to its execution queue. |
TRANSFER_RESULTS_TO_ORCH |
4 | The task finished executing and its results should return to the orchestrator. |
RESULT_RETURN_FINISHED |
5 | Results reached the source device; the task is complete. |
SEND_TO_ORCH |
6 | A generated task is sent to its orchestrator. |
UPDATE_REAL_TIME_CHARTS |
7 | Refreshes the live visualization. |
SEND_TASK_FROM_ORCH_TO_DESTINATION |
8 | The orchestrator made a decision and the request travels to the destination. |
NEXT_BATCH |
9 | Schedules the next batch of generated tasks. |
The full flow through these tags is described in Task lifecycle.
Task batching
Generating hundreds of thousands of events at time zero would make the queue large and slow. Instead, DefaultSimulationManager schedules only the first batch_size tasks at the start of the simulation. When that batch has been scheduled, a NEXT_BATCH event schedules the next one, and so on. Batching does not change the simulated task arrival times; it only changes when their events enter the queue.
Termination
The manager schedules PRINT_LOG at simulationDuration. When it fires, the manager checks whether all generated tasks have been accounted for. If wait_for_all_tasks=true, it reschedules PRINT_LOG every 10 simulated seconds until every task has succeeded or failed. This can extend the simulated time beyond the configured duration, because a task already in execution is allowed to finish.
If wait_for_all_tasks=false, the simulation terminates at the configured duration even if tasks are still in flight. Tasks not completed are counted in the results according to their state at termination. This is why the documentation recommends leaving the option enabled unless simulation time is a serious constraint.
State and determinism
Each iteration creates new instances of the engine, the manager, all models, and the logger. The only shared state is the static configuration in SimulationParameters and the application list, both of which are parsed once and then read-only. This design makes iterations independent.
However, there are two practical caveats:
- Randomness is not seeded explicitly. Task generation and mobility use a
Randominstance. Runs are not guaranteed to be bit-for-bit reproducible across JVMs or after code changes. If you need reproducible experiments, set a seed in a custom task generator or custom mobility model. - Parallel runs share the JVM. Static state is read-only in normal operation, but custom models that write to static fields can introduce cross-run interference. Keep custom model state in instance fields.
Where to plug in
The Simulation class exposes setters for each replaceable model. The table below lists the class you subclass, the constructor your subclass must provide, and the behavior you normally override.
| Extension point | Setter | Required constructor | Main methods |
|---|---|---|---|
| Mobility model | setCustomMobilityModel |
(SimulationManager, Location) |
updateLocation, distanceTo |
| Computing node | setCustomComputingNode |
(SimulationManager, double mipsPerCore, int cores, double storage, double ram) |
Device state, execution, resource management |
| Task generator | setCustomTaskGenerator |
(SimulationManager) |
generate |
| Orchestrator | setCustomEdgeOrchestrator |
(SimulationManager) |
findComputingNode, resultsReturned |
| Network model | setCustomNetworkModel |
(SimulationManager) |
processEvent, transferFinished |
| Simulation manager | setCustomSimulationManager |
(SimLog, PureEdgeSim, int, int, Scenario) |
startSimulation, processEvent |
| Topology creator | setCustomTopologyCreator |
(SimulationManager, ComputingNodesGenerator) |
generateTopologyGraph |
| Computing nodes generator | setCustomComputingNodesGenerator |
(SimulationManager, Class<? extends MobilityModel>, Class<? extends ComputingNode>) |
generateDatacentersAndDevices |
The exact signatures are documented in Customizing models and in the Javadoc.
Output architecture
Output is produced in three places:
SimLogaccumulates metrics during the run, prints the console report, and writes the CSV and optional text file. OneSimLogexists per iteration.ChartsGeneratorreads the completed CSV and produces aggregate comparison charts underFinal results/, grouped by algorithm and by architecture.SimulationVisualizerdraws the live charts and, when enabled, saves per-iteration images.
The separation matters for reproducibility: the CSV is the authoritative result. Charts are derived from it. You can re-plot results without rerunning the simulation.
Related reading
- Task lifecycle: the event flow in detail.
- Simulation models: how each default model computes its behavior.
- Customizing models: practical extension recipes.
- API reference: package and class-level guidance.