Skip to content

Commit

Permalink
⏱️ Estimate for package install and load time (#2672)
Browse files Browse the repository at this point in the history
  • Loading branch information
fonsp authored Oct 22, 2023
1 parent 5df9cc5 commit fb7ca86
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 4 deletions.
99 changes: 99 additions & 0 deletions frontend/common/InstallTimeEstimate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useEffect, useState } from "../imports/Preact.js"
import _ from "../imports/lodash.js"

const loading_times_url = `https://julia-loading-times-test.netlify.app/pkg_load_times.csv`
const package_list_url = `https://julia-loading-times-test.netlify.app/top_packages_sorted_with_deps.txt`

/** @typedef {{ install: Number, precompile: Number, load: Number }} LoadingTime */

/**
* @typedef PackageTimingData
* @type {{
* times: Map<String,LoadingTime>,
* packages: Map<String,String[]>,
* }}
*/

/** @type {{ current: Promise<PackageTimingData>? }} */
const data_promise_ref = { current: null }

export const get_data = () => {
if (data_promise_ref.current != null) {
return data_promise_ref.current
} else {
const times_p = fetch(loading_times_url)
.then((res) => res.text())
.then((text) => {
const lines = text.split("\n")
const header = lines[0].split(",")
return new Map(
lines.slice(1).map((line) => {
let [pkg, ...times] = line.split(",")

return [pkg, { install: Number(times[0]), precompile: Number(times[1]), load: Number(times[2]) }]
})
)
})

const packages_p = fetch(package_list_url)
.then((res) => res.text())
.then(
(text) =>
new Map(
text.split("\n").map((line) => {
let [pkg, ...deps] = line.split(",")
return [pkg, deps]
})
)
)

data_promise_ref.current = Promise.all([times_p, packages_p]).then(([times, packages]) => ({ times, packages }))

return data_promise_ref.current
}
}

export const usePackageTimingData = () => {
const [data, set_data] = useState(/** @type {PackageTimingData?} */ (null))

useEffect(() => {
get_data().then(set_data)
}, [])

return data
}

const recursive_deps = (/** @type {PackageTimingData} */ data, /** @type {string} */ pkg, found = []) => {
const deps = data.packages.get(pkg)
if (deps == null) {
return []
} else {
const newfound = _.union(found, deps)
return [...deps, ..._.difference(deps, found).flatMap((dep) => recursive_deps(data, dep, newfound))]
}
}

export const time_estimate = (/** @type {PackageTimingData} */ data, /** @type {string[]} */ packages) => {
let deps = packages.flatMap((pkg) => recursive_deps(data, pkg))
let times = _.uniq([...packages, ...deps])
.map((pkg) => data.times.get(pkg))
.filter((x) => x != null)

console.log({ packages, deps, times, z: _.uniq([...packages, ...deps]) })
let sum = (xs) => xs.reduce((acc, x) => acc + (x == null || isNaN(x) ? 0 : x), 0)

return {
install: sum(times.map(_.property("install"))) * timing_weights.install,
precompile: sum(times.map(_.property("precompile"))) * timing_weights.precompile,
load: sum(times.map(_.property("load"))) * timing_weights.load,
}
}

const timing_weights = {
// Because the GitHub Action runner has superfast internet
install: 2,
// Because the GitHub Action runner has average compute speed
load: 1,
// Because precompilation happens in parallel
precompile: 0.3,
}
13 changes: 10 additions & 3 deletions frontend/components/EditOrRunButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,17 @@ const expected_runtime_str = (/** @type {import("./Editor.js").NotebookData} */
}

const sec = _.round(runtime_overhead + ex * runtime_multiplier, -1)
return pretty_long_time(sec)
}

export const pretty_long_time = (/** @type {number} */ sec) => {
const min = sec / 60
const sec_r = Math.ceil(sec)
const min_r = Math.round(min)

if (sec < 60) {
return `${Math.ceil(sec)} second${sec > 1 ? "s" : ""}`
return `${sec_r} second${sec_r > 1 ? "s" : ""}`
} else {
const min = sec / 60
return `${Math.ceil(min)} minute${min > 1 ? "s" : ""}`
return `${min_r} minute${min_r > 1 ? "s" : ""}`
}
}
1 change: 1 addition & 0 deletions frontend/components/PkgStatusMark.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const package_status = ({ nbpkg, package_name, available_versions, is_dis
}

/**
* The little icon that appears inline next to a package import in code (e.g. `using PlutoUI ✅`)
* @param {{
* package_name: string,
* pluto_actions: any,
Expand Down
15 changes: 14 additions & 1 deletion frontend/components/Popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { RawHTMLContainer, highlight } from "./CellOutput.js"
import { PlutoActionsContext } from "../common/PlutoContext.js"
import { package_status, nbpkg_fingerprint_without_terminal } from "./PkgStatusMark.js"
import { PkgTerminalView } from "./PkgTerminalView.js"
import { useDebouncedTruth } from "./RunArea.js"
import { prettytime, useDebouncedTruth } from "./RunArea.js"
import { time_estimate, usePackageTimingData } from "../common/InstallTimeEstimate.js"
import { pretty_long_time } from "./EditOrRunButton.js"

// This funny thing is a way to tell parcel to bundle these files..
// Eventually I'll write a plugin that is able to parse html`...`, but this is it for now.
Expand Down Expand Up @@ -174,6 +176,11 @@ const PkgPopup = ({ notebook, recent_event, clear_recent_event, disable_input })

const showupdate = pkg_status?.offer_update ?? false

const timingdata = usePackageTimingData()
const estimate = timingdata == null || recent_event?.package_name == null ? null : time_estimate(timingdata, [recent_event?.package_name])
const total_time = estimate == null ? 0 : estimate.install + estimate.load + estimate.precompile
const total_second_time = estimate == null ? 0 : estimate.load

// <header>${recent_event?.package_name}</header>
return html`<pkg-popup
class=${cl({
Expand All @@ -183,6 +190,12 @@ const PkgPopup = ({ notebook, recent_event, clear_recent_event, disable_input })
})}
>
${pkg_status?.hint ?? "Loading..."}
${(pkg_status?.status === "will_be_installed" || pkg_status?.status === "busy") && total_time > 10
? html`<div class="pkg-time-estimate">
Installation can take <strong>${pretty_long_time(total_time)}</strong>${`. `}<br />${`Afterwards, it loads in `}
<strong>${pretty_long_time(total_second_time)}</strong>.
</div>`
: null}
<div class="pkg-buttons">
<a
class="pkg-update"
Expand Down
8 changes: 8 additions & 0 deletions frontend/editor.css
Original file line number Diff line number Diff line change
Expand Up @@ -1957,6 +1957,14 @@ pkg-popup .toggle-terminal {
right: 20px;
}

.pkg-time-estimate {
font-size: 0.8em;
margin: 0.5em 0em;
padding: 0.5em 0.5em;
background: var(--pluto-logs-info-color);
border-radius: 0.5em;
}

pkg-terminal {
display: block;
/* width: 20rem; */
Expand Down

0 comments on commit fb7ca86

Please sign in to comment.