Skip to content
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

implement TrackedResource #94

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# News

## v1.2.0 - 2023-08-03

- Rudimentary utilization tracker for resources is now implemented as the `TrackedResource` wrapper, together with a converter `DataFrame(::TrackedResource)` (thanks to implementing the `Tables.jl` API).

## v1.1.0 - 2023-08-02

- Start using `Base`'s API: `Base.unlock`, `Base.islocked`, `Base.isready`, `Base.put!`, `Base.take!`. Deprecate `put`, `release`. Moreover, consider using `Base.take!` instead of `Base.get` (which was not deprecated yet, as we decide which semantics to follow). Lastly, `Base.lock` and `Base.trylock` are **not** implement -- they are superficially similar to `request` and `tryrequest`, but have to be explicitly `@yield`-ed.
Expand Down
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ license = "MIT"
desc = "A discrete event process oriented simulation framework."
authors = ["Ben Lauwens and SimJulia and ConcurrentSim contributors"]
repo = "https://github.com/JuliaDynamics/ConcurrentSim.jl.git"
version = "1.1.0"
version = "1.2.0"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"

[compat]
DataStructures = "0.18"
ResumableFunctions = "0.6"
Tables = "1.10.1"
julia = "1.6"
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
ConcurrentSim = "6ed1e86c-fcaf-46a9-97e0-2b26a2cdb499"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184"
Expand Down
2 changes: 2 additions & 0 deletions src/ConcurrentSim.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module ConcurrentSim
export AbstractProcess, Simulation, run, now, active_process, StopSimulation
export Process, @process, interrupt
export Container, Resource, Store, put!, get, cancel, request, tryrequest
export TrackedResource
export nowDatetime

include("base.jl")
Expand All @@ -28,6 +29,7 @@ module ConcurrentSim
include("resources/base.jl")
include("resources/containers.jl")
include("resources/stores.jl")
include("resources/tracked.jl")
include("utils/time.jl")
include("deprecated.jl")
end
101 changes: 101 additions & 0 deletions src/resources/tracked.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import Tables

"""
A wrapper around a resource that tracks the time at which the resource is interacted with.

```jldoctest
julia> using ConcurrentSim, DataFrames

julia> env = Simulation(); r = TrackedResource(Resource(env));

julia> run(env, 1); now(env)
1.0

julia> request(r); run(env, 2); now(env)
2.0

julia> request(r); run(env, 3); unlock(r); run(env, 4); now(env)
4.0

julia> DataFrame(r)
3×2 DataFrame
Row │ events times
│ Symbol Float64
─────┼───────────────────
1 │ increase 1.0
2 │ increase 2.0
3 │ decrease 3.0
```
"""
struct TrackedResource{T}
resource::T
events::Vector{Symbol}
times::Vector{Float64}
end

TrackedResource(resource) = TrackedResource(resource, Symbol[], Float64[])

function Base.take!(tr::TrackedResource, args...; kwargs...)
r = take!(tr.resource, args...; kwargs...)
push!(tr.events, :decrease)
push!(tr.times, now(tr.resource.env))
return r
end

function Base.put!(tr::TrackedResource, args...; kwargs...)
r = put!(tr.resource, args...; kwargs...)
push!(tr.events, :increase)
push!(tr.times, now(tr.resource.env))
return r
end

function Base.unlock(tr::TrackedResource, args...; kwargs...)
r = unlock(tr.resource, args...; kwargs...)
push!(tr.events, :decrease)
push!(tr.times, now(tr.resource.env))
return r
end

function request(tr::TrackedResource, args...; kwargs...)
r = request(tr.resource, args...; kwargs...)
push!(tr.events, :increase)
push!(tr.times, now(tr.resource.env))
return r
end

##
# Tables interface
##

Tables.istable(::Type{<:TrackedResource}) = true
Tables.schema(tr::TrackedResource) = Tables.Schema(Tables.columnnames(tr), [Symbol, Float64])

Tables.columnaccess(::Type{<:TrackedResource}) = true

Tables.columns(tr::TrackedResource) = tr

function Tables.getcolumn(tr::TrackedResource, i::Int)
if i == 1
return tr.events
elseif i == 2
return tr.times
else
error("`TrackedResource` has only two columns, but you are attempting to access column $(i).")
end
end

function Tables.getcolumn(tr::TrackedResource, s::Symbol)
if s == :events
return tr.events
elseif s == :times
return tr.times
else
error("`TrackedResource` has only two columns (events and times), but you are attempting to access column $(s).")
end
end

function Tables.columnnames(tr::TrackedResource)
return (:events, :times)
end

# TODO Makie recipes
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[deps]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Expand All @@ -9,4 +10,5 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ResumableFunctions = "c5292f4c-5179-55e1-98c5-05642aab7184"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA
@doset "resources_containers_deprecated"
@doset "resources_stores"
@doset "resources_stores_deprecated"
@doset "resources_tracked"
@doset "resource_priorities"
@doset "utils_time"
VERSION >= v"1.9" && @doset "doctests"
Expand Down
42 changes: 42 additions & 0 deletions test/test_resources_tracked.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using ConcurrentSim
using ResumableFunctions
using Test
using DataFrames, Tables

struct StoreObject
i :: Int
end

@resumable function my_consumer(sim::Simulation, sto)
for j in 1:10
@yield timeout(sim, rand())
println("$(now(sim)), consumer is demanding object")
obj = @yield take!(sto)
println("$(now(sim)), consumer is being served with object ", obj.i)
end
end

@resumable function my_producer(sim::Simulation, sto)
for j in 1:10
println("$(now(sim)), producer is offering object $j")
@yield put!(sto, StoreObject(j))
println("$(now(sim)), producer is being served")
@yield timeout(sim, 2*rand())
end
end

sim = Simulation()
sto = TrackedResource(Store{StoreObject}(sim))
@process my_consumer(sim, sto)
@process my_producer(sim, sto)
run(sim)

df = DataFrame(sto)
@test df[!,:events] == Tables.getcolumn(sto, 1)
@test df[!,:times] == Tables.getcolumn(sto, 2)

@test_throws ErrorException Tables.getcolumn(sto, 3)
@test_throws ErrorException Tables.getcolumn(sto, :lala)

@test Tables.columnaccess(typeof(sto))
Tables.schema(sto)
Loading