add DNS traffic to stats page

This commit is contained in:
Jesse Plamondon-Willard
2026-06-24 15:46:27 -04:00
parent d691aeba15
commit 40611258bf
4 changed files with 134 additions and 5 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ _ReSharper*/
appsettings.Development.json
# auto-generated files
src/SMAPI.Web/wwwroot/Content/data/*.jsonl
src/SMAPI.Web/wwwroot/Content/data
src/SMAPI.Web/Properties/PublishProfiles
src/SMAPI.Web/Properties/ServiceDependencies
+1
View File
@@ -280,6 +280,7 @@ internal class BackgroundService : IHostedService, IDisposable
File.Copy(BackgroundService.ModDatasetRepo.GetFilePath("stats/mods by type.jsonl"), copyToPath, overwrite: true);
File.Copy(BackgroundService.ModDatasetRepo.GetFilePath("stats/Content Patcher packs by format version.jsonl"), Path.Combine(BackgroundService.WebRootPath, "Content", "data", "content-packs-by-format.jsonl"), overwrite: true);
File.Copy(BackgroundService.ModDatasetRepo.GetFilePath("reference-data/SMAPI costs.jsonl"), Path.Combine(BackgroundService.WebRootPath, "Content", "data", "smapi-costs.jsonl"), overwrite: true);
File.Copy(BackgroundService.ModDatasetRepo.GetFilePath("reference-data/SMAPI DNS queries.json"), Path.Combine(BackgroundService.WebRootPath, "Content", "data", "smapi-dns-queries.json"), overwrite: true);
context.WriteLine("Done!");
}
+10 -1
View File
@@ -20,7 +20,8 @@
smapi.statsPage({
modsByTypeUri: @this.ForJson(Url.ContentWithCacheBust("~/Content/data/mods-by-type.jsonl")),
contentPacksByFormatUri: @this.ForJson(Url.ContentWithCacheBust("~/Content/data/content-packs-by-format.jsonl")),
smapiCostsUri: @this.ForJson(Url.ContentWithCacheBust("~/Content/data/smapi-costs.jsonl"))
smapiCostsUri: @this.ForJson(Url.ContentWithCacheBust("~/Content/data/smapi-costs.jsonl")),
smapiDnsQueriesUri: @this.ForJson(Url.ContentWithCacheBust("~/Content/data/smapi-dns-queries.json"))
});
});
</script>
@@ -35,6 +36,7 @@
<li><a href="#mods-by-type">Mods by type</a></li>
<li><a href="#new-mods">New mods</a></li>
<li><a href="#content-patcher-packs">Content Patcher packs</a></li>
<li><a href="#web-traffic">Web traffic</a></li>
<li><a href="#smapi-costs">SMAPI costs</a></li>
</ul>
</nav>
@@ -101,6 +103,13 @@
<div class="chart pie-chart" id="contentPacksByFormat"></div>
<div class="chart line-chart" id="contentPacksByFormatOverTime"></div>
@* web traffic *@
<h2 id="web-traffic">Web traffic</h2>
<div class="last-updated">Based on data from {{dnsQueries.lastUpdated}}.</div>
<div class="chart-intro">SMAPI's web services received about {{(dnsQueries.latestCount / 1000000).toFixed(1)}} million DNS queries in {{dnsQueries.lastUpdated}}. This serves as a rough indicator of player and mod author activity, since SMAPI doesn't collect any telemetry.</div>
<div class="chart" id="dnsQueriesOverTime"></div>
@* SMAPI costs *@
<h2 id="smapi-costs">SMAPI costs</h2>
<div class="last-updated">Based on data from {{costs.lastUpdated}}. This information is delayed due to billing cycles.</div>
+122 -3
View File
@@ -67,6 +67,20 @@ smapi.statsPage = function (options) {
}
},
// configure the 'web traffic' stats
dnsQueries: {
// notable events which may affect traffic, indexed by their date in smapi-dns-queries.json
notableEvents: {
"2018-05": "Stardew Valley 1.3 beta", // 30 April 2018
"2018-08": "Stardew Valley 1.3", // 01 August 2018
"2019-12": "Stardew Valley 1.4", // 26 November 2019
"2020-12": "Stardew Valley 1.5", // 21 December 2020
"2021-08": "Stardew Valley 1.5.5", // 17 August 2021
"2024-03": "Stardew Valley 1.6", // 19 March 2024
"2024-11": "Stardew Valley 1.6.9" // 04 November 2024
}
},
// configure the 'SMAPI costs' stats
costs: {
// colors for each service (omitted entries cycle through the colorPalette)
@@ -110,6 +124,10 @@ smapi.statsPage = function (options) {
xLabels: [],
versions: []
},
dnsQueries: {
xLabels: [],
values: []
},
costs: {
rows: [],
serviceKeys: [],
@@ -147,6 +165,10 @@ smapi.statsPage = function (options) {
total: 0,
topVersions: []
},
dnsQueries: {
lastUpdated: null,
latestCount: 0
},
costs: {
lastUpdated: null,
previousCost: 0,
@@ -187,12 +209,13 @@ smapi.statsPage = function (options) {
/*********
** Fetch data files
*********/
const [modsByTypeRows, contentPacksRows, costsRows] = await Promise.all([
const [modsByTypeRows, contentPacksRows, costsRows, dnsData] = await Promise.all([
fetchJsonLinesAsync(options.modsByTypeUri),
fetchJsonLinesAsync(options.contentPacksByFormatUri),
fetchJsonLinesAsync(options.smapiCostsUri)
fetchJsonLinesAsync(options.smapiCostsUri),
fetchJsonAsync(options.smapiDnsQueriesUri)
]);
if (!modsByTypeRows || !contentPacksRows || !costsRows) {
if (!modsByTypeRows || !contentPacksRows || !costsRows || !dnsData) {
this.isDataFailed = true;
return;
}
@@ -319,6 +342,25 @@ smapi.statsPage = function (options) {
}
/*********
** Process DNS queries
*********/
{
const xLabels = Object.keys(dnsData);
const values = Object.values(dnsData);
data.dnsQueries = {
xLabels,
values
};
this.dnsQueries = {
lastUpdated: formatMonthYear(xLabels[xLabels.length - 1]),
latestCount: values[values.length - 1]
};
}
/*********
** Process SMAPI costs
*********/
@@ -858,6 +900,70 @@ smapi.statsPage = function (options) {
}
/*********
** 'DNS queries over time' bar chart
*********/
{
const curData = data.dnsQueries;
const chart = echarts.init(document.getElementById("dnsQueriesOverTime"));
chart.setOption({
title: {
text: "Web traffic",
textStyle: createTitleStyle()
},
grid: {
top: 55,
bottom: this.showAdvancedControls ? 80 : 60,
left: 70,
right: 20
},
xAxis: {
type: "category",
data: curData.xLabels,
axisLabel: {
rotate: 90,
fontSize: 11
}
},
yAxis: {
type: "value",
name: "DNS queries (millions)",
axisLabel: {
formatter: value => (value / 1_000_000).toFixed(0)
}
},
series: [
{
type: "bar",
data: curData.values,
color: "#3388cc",
markLine: createMarkLine(p => p.name, config.dnsQueries.notableEvents)
}
],
tooltip: {
trigger: "axis",
formatter: params => {
const value = params[0].value;
const lines = [
params[0].axisValue, // date
value > 1_000_000
? `${(Math.round(value / 100_000) / 10).toLocaleString()} million`
: value.toLocaleString()
];
return lines.join("<br />");
}
},
toolbox: createToolbox(true, this.showAdvancedControls)
});
charts.push(chart);
}
/*********
** 'SMAPI costs over time' stacked bar chart
*********/
@@ -960,6 +1066,19 @@ smapi.statsPage = function (options) {
return rawJsonLines.trim().split(/\r?\n/).filter(row => !!row).map(JSON.parse);
}
/**
* Fetch and parse data from a JSON URI.
* @param {string} fetchUri The URI from which to fetch the JSON data.
* @returns {null|object} Returns the parsed data if it was found, else `null`.
*/
async function fetchJsonAsync(fetchUri) {
const response = await fetch(fetchUri);
if (!response.ok)
return null;
return await response.json();
}
/**
* Get a formatted date string.
* @param {Date|string} date The ISO 8601 date-only string to format.