Public
Edited
May 31, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// utcTickInterval = d3.utcTickInterval(_start, _stop, bins)
Insert cell
Insert cell
Insert cell
Insert cell
// a wrapper around d3.reduce

reduceConnections = (_connections, startingValue = 0) => {
_; // force to re-generate the timeline as new statuses are known

if (_connections.length <= 1) return _connections;

let reducedTimeline = d3
.sort(_connections, (conn) => conn.timestamp)
// .slice(1)
.reduce(
(accumulator, conn, index, array) => {
const next = array[index + 1];
const previous = array[index - 1];
let datum = {
timestamp: conn.timestamp,
value:
conn.event === "disconnect"
? accumulator[accumulator.length - 1].value - 1
: accumulator[accumulator.length - 1].value + 1
};
if (next) datum.timestampUntil = next.timestamp;
else datum.timestampUntil = maxStatus; ////////////////

// we keep only one datum for the same second
if (
// next &&
datum.timestamp === accumulator[accumulator.length - 1].timestamp
) {
const popped = accumulator.pop();
// console.log(datum, popped, conn);
}
accumulator.push(datum);

return accumulator;
},
// reduction starts with a 1-item array
[
{
timestamp: _start.getTime() / 1000, //d3.min(_connections, (conn) => conn.timestamp),
value: startingValue,
timestampUntil: d3.sort(_connections, (conn) => conn.timestamp)[1]
.timestamp
}
]
);

// let's fake the last timeline point until $stop or until $latest_7000, whichever happens first
reducedTimeline.push({
timestamp: d3.min([latest_7000, _stop.getTime() / 1000]),
value: d3.sort(reducedTimeline, (r) => r.timestamp)[
reducedTimeline.length - 1
].value
});

return reducedTimeline;
}
Insert cell
d3.rollup(
connections.filter((c) => c.prb_id === 24671),
(arr) => arr.length,
(c) => c.event
)
Insert cell
conns = d3.rollup(
connections,
(arr) => arr.length,
(c) => c.event,
(c) => c.prb_id
)
Insert cell
Insert cell
d3.rollup(
connections,
(arr) => arr.length,
(conn) => conn.prb_id
)
Insert cell
Array.from(
d3.rollup(
connections,
(arr) => ({
diff:
arr.filter((a) => a.event === "connect").length -
arr.filter((a) => a.event === "disconnect").length,
length: arr.length
}),
(c) => c.prb_id
)
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// viewof show_zero = Radio([true, false], {
// label: "Show 0 (in y axis)",
// value: query["show_zero"] ? query["show_zero"] : defaults.show_zero
// })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// maxTaggedUntaggedCombined = taggedUntaggedCombined.reduce((accumulator, currentValue) => {
// if (currentValue.tagged > accumulator) accumulator = currentValue.tagged;
// if (currentValue.untagged > accumulator) accumulator = currentValue.untagged;
// return accumulator;
// }, 0)
Insert cell
Insert cell
// overlapsOfInterest = statuses.map((probeStatuses) => {
// let positiveOverlap = probeStatuses
// .filter((probeStatus) => probeStatus.status === "down")
// .map((probeStatus) => {
// const t1 = d3.max([
// momentOfInterest[0].getTime() / 1000,
// probeStatus.start
// ]);
// const t2 = d3.min([
// momentOfInterest[1].getTime() / 1000,
// probeStatus.end
// ]);
// return t2 - t1;
// })
// .filter((overlap) => overlap > 0);

// positiveOverlap = d3.sum(positiveOverlap);
// probeStatuses[0].prb_id;
// return { prb_id: probeStatuses[0].prb_id, positiveOverlap };
// })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
/*
connections come from two sources:
1. Probe status as reported by the probe-archive on the day before `start`
2. Connect/disconnect events as reported by measurement ID 7000
*/
connections = {
let probeChunks = chunks(probes, 500);

let promises = probeChunks.map((chunk) => {
let url = `https://atlas.ripe.net:443/api/v2/measurements/7000/results/?start=${start}T00:00&stop=${stop}T00:00&probe_ids=${chunk.join(
","
)}`;
console.log(url);
return fetch(url).then((r) => r.json());
});

let conns = await Promise.all(promises);

let probeArchiveConns = probe_objects.map((p) => {
// if (p.status.since <= _start.getTime() / 1000)
return {
prb_id: p.id,
event: p.status.name === "Connected" ? "connect" : "disconnect",
timestamp: _start.getTime() / 1000, //p.status.since,
asn: p.asn_v4 || p.asn_v6,
prefix: p.prefix_v4 || p.prefix_v6
};
});
return probeArchiveConns
.concat(conns.flat())
.filter((c) => c.prb_id === 24671);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable statuses = Object.entries(groupBy(connections, "prb_id")).map(
([prb_id, conn_result]) => {
mutable description = `Recreating connection states for prb=${prb_id}`;

let probe = probe_objects.filter((p) => p.id === parseInt(prb_id))[0];
let conns_sorted = conn_result.sort(sortByTimestamp);
// sequential pass over timestamps
// and interpolating connection state
let state = (event1, event2) =>
event1 === "connect" && event2 === "disconnect" ? "up" : "down";
let flip = (event) => (event === "connect" ? "disconnect" : "connect");

let prefix_v4_size = probe.prefix_v4
? 2 ** (32 - parseInt(probe.prefix_v4.split("/")[1]))
: 0;
let res = [];

// let's fake the first status
let r = {
start: _start.getTime() / 1000, //Math.min(...timestamps),
end: conns_sorted[0].timestamp,
status: state(flip(conns_sorted[0].event), conns_sorted[0].event),
prb_id: prb_id,
asn_v4: probe.asn_v4,
prefix_v4: probe.prefix_v4,
prefix_v4_size: prefix_v4_size,
country_code: probe.country_code,
tags: probe.tags.map((t) => t.slug)
};
res.push(r);

for (let i = 0; i < conns_sorted.length; i++) {
let start = conns_sorted[i];
let end = conns_sorted[i + 1];

if (!start || !end) continue;

let status = {
start: start.timestamp,
end: end.timestamp,
status: state(start.event, end.event),
prb_id: prb_id,
asn_v4: probe.asn_v4,
prefix_v4: probe.prefix_v4,
prefix_v4_size: prefix_v4_size,
country_code: probe.country_code,
tags: probe.tags.map((t) => t.slug)
};
res.push(status);
}

// let's fake the last status until $stop or until $latest_7000, whichever happens first
let fakeStop =
d3
.min([new Date(_stop.getTime()), new Date(latest_7000 * 1000)])
.getTime() / 1000;
const last = conns_sorted[conns_sorted.length - 1];
let s = {
start: last.timestamp,
end: fakeStop,
status: state(last.event, flip(last.event)),
prb_id: prb_id,
asn_v4: probe.asn_v4,
prefix_v4: probe.prefix_v4,
prefix_v4_size: prefix_v4_size,
country_code: probe.country_code,
tags: probe.tags.map((t) => t.slug)
};
res.push(s);

mutable _ += 1;

return res;
}
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// we're exceeding the sessionStorage quota :(
// cached_fetch = async (url) => {
// let cached = localStorage.getItem(url);
// if (cached) {
// console.log(`cache hit ${url} ${cached}`);
// return JSON.parse(cached);
// }

// console.log(`cache miss ${url}`);
// let r = await d3.json(url);

//
// // localStorage.setItem(url, JSON.stringify(r));
// return r;
// }
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
defaults = {
return {
asn: 3333,
country: "NL,BE",
location: "Amsterdam, The Netherlands",
radius: 50, // km
queryFilter: "country", // default field to query for
dim: false,
state: "up",
show_zero: true,
overlayInfrastructure: false
};
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
import { on } from "@fil/plot-onclick-experimental-plugin@255"
Insert cell
// sometimes the OpenStreetMap Nominatim API is down
// net::ERR_ADDRESS_UNREACHABLE?
// fallback to selecting a place on the map by hand
// viewof _coordinates = worldMapCoordinates({
// value: nominatim_down ? [-122.27, 37.87] : [0, 0]
// })
Insert cell
Insert cell
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more