Public
Edited
Oct 21
Paused
Importers
Insert cell
Insert cell
[object["last-used"], object.radius]
Insert cell
object
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)
.reduce(
(accumulator, conn, index, array) => {
const next = array[index + 1];
const previous = array[index - 1];
let value,
valueAccumulated = accumulator[accumulator.length - 1];

if (previous === undefined) return accumulator;

if (conn.event === "disconnect") value = valueAccumulated.value - 1;
else if (conn.event === "connect") value = valueAccumulated.value + 1;
else value = valueAccumulated.value;

let datum = {
timestamp: conn.timestamp,
value: value
};
// if (next) datum.timestampUntil = next.timestamp;
// else datum.timestampUntil = maxStatus; ////////////////

// we keep only one datum for the same second
// if (next && datum.timestamp === next.timestamp) {
// accumulator.pop();
// // console.log("same timestamp:", datum, next);
// }
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
Insert cell
// startConnected = statuses
// .map((t) =>
// t.filter(
// (s) =>
// s.start !== s.end &&
// new Date(s.start * 1000).valueOf() === _start.valueOf() &&
// s.status === "up"
// )
// )
// .flat()
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
probeArchiveStart = {
mutable description = `Fetching all probes`;

let dayBefore = structuredClone(_start);
dayBefore.setDate(dayBefore.getDate() - 1);

return d3
.json(
`https://atlas.ripe.net/api/v2/probes/archive/?format=json&date=${
dayBefore.toISOString().split("T")[0]
}&status=1,2`
)
.then((r) => r.results);
}
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) =>
fetch(
`https://atlas.ripe.net:443/api/v2/measurements/7000/results/?start=${start}T00:00&stop=${stop}T00:00&probe_ids=${chunk.join(
","
)}`
).then((r) => r.json())
);

let conns = await Promise.all(promises);

let probeArchiveConns = probe_objects.map((p) => {
// fake the status reported by msm 7000 at :00:00 of _start
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());
}
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;

// mutable description = "Done.";
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
// 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
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
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