Export map tiles usage to Prometheus

This commit is contained in:
Eugene Burmakin
2025-02-11 20:45:36 +01:00
parent d2d6f95322
commit 1580fb8ade
12 changed files with 152 additions and 12 deletions

View File

@@ -16,6 +16,13 @@ To set a custom tile URL, go to the user settings and set the `Maps` section to
- Safe settings for user with default values.
- In the user settings, you can now set a custom tile URL for the map. #429 #715
- If you have Prometheus exporter enabled, you can now see a `ruby_dawarich_map_tiles` metric in Prometheus, which shows the total number of map tiles loaded. Example:
```
# HELP ruby_dawarich_map_tiles
# TYPE ruby_dawarich_map_tiles gauge
ruby_dawarich_map_tiles 99
```
# 0.24.0 - 2025-02-10

View File

@@ -1,2 +1,2 @@
prometheus_exporter: bundle exec prometheus_exporter -b ANY
web: bin/rails server -p 3000 -b ::
web: bin/rails server -p 3000 -b ::

View File

@@ -28,6 +28,7 @@ Donate using crypto: [0x6bAd13667692632f1bF926cA9B421bEe7EaEB8D4](https://ethers
- Explore statistics like the number of countries and cities visited, total distance traveled, and more!
📄 **Changelog**: Find the latest updates [here](CHANGELOG.md).
👩‍💻 **Contribute**: See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute to Dawarich.
---

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
class Api::V1::TileUsagesController < ApiController
def create
TileUsage::Track.new(params[:tile_count].to_i).call
head :ok
end
end

View File

@@ -8,9 +8,7 @@ import { createMarkersArray } from "../maps/markers";
import {
createPolylinesLayer,
updatePolylinesOpacity,
updatePolylinesColors,
calculateSpeed,
getSpeedColor
updatePolylinesColors
} from "../maps/polylines";
import { fetchAndDrawAreas, handleAreaCreated } from "../maps/areas";
@@ -32,9 +30,13 @@ import { countryCodesMap } from "../maps/country_codes";
import "leaflet-draw";
import { initializeFogCanvas, drawFogCanvas, createFogOverlay } from "../maps/fog_of_war";
import { TileMonitor } from "../maps/tile_monitor";
export default class extends Controller {
static targets = ["container"];
static values = {
monitoringEnabled: Boolean
}
settingsButtonAdded = false;
layerControl = null;
@@ -245,6 +247,19 @@ export default class extends Controller {
if (this.liveMapEnabled) {
this.setupSubscription();
}
// Initialize tile monitor
this.tileMonitor = new TileMonitor(this.monitoringEnabledValue, this.apiKey);
// Add tile load event handlers to each base layer
Object.entries(this.baseMaps()).forEach(([name, layer]) => {
layer.on('tileload', () => {
this.tileMonitor.recordTileLoad(name);
});
});
// Start monitoring
this.tileMonitor.startMonitoring();
}
disconnect() {
@@ -260,6 +275,11 @@ export default class extends Controller {
if (this.map) {
this.map.remove();
}
// Stop tile monitoring
if (this.tileMonitor) {
this.tileMonitor.stopMonitoring();
}
}
setupSubscription() {

View File

@@ -0,0 +1,67 @@
export class TileMonitor {
constructor(monitoringEnabled, apiKey) {
this.monitoringEnabled = monitoringEnabled;
this.apiKey = apiKey;
this.tileQueue = 0;
this.tileUpdateInterval = null;
}
startMonitoring() {
// Only start the interval if monitoring is enabled
if (!this.monitoringEnabled) return;
// Clear any existing interval
if (this.tileUpdateInterval) {
clearInterval(this.tileUpdateInterval);
}
// Set up a regular interval to send stats
this.tileUpdateInterval = setInterval(() => {
this.sendTileUsage();
}, 5000); // Exactly every 5 seconds
}
stopMonitoring() {
if (this.tileUpdateInterval) {
clearInterval(this.tileUpdateInterval);
this.sendTileUsage(); // Send any remaining stats
}
}
recordTileLoad() {
if (!this.monitoringEnabled) return;
this.tileQueue += 1;
}
sendTileUsage() {
// Don't send if monitoring is disabled or queue is empty
if (!this.monitoringEnabled || this.tileQueue === 0) return;
const currentCount = this.tileQueue;
console.log('Sending tile usage batch:', currentCount);
fetch('/api/v1/tile_usages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: JSON.stringify({
tile_count: currentCount
})
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Only subtract sent count if it hasn't changed
if (this.tileQueue === currentCount) {
this.tileQueue = 0;
} else {
this.tileQueue -= currentCount;
}
console.log('Tile usage batch sent successfully');
})
.catch(error => console.error('Error recording tile usage:', error));
}
}

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
class TileUsage::Track
def initialize(count = 1)
@count = count
end
def call
metric_data = {
type: 'counter',
name: 'dawarich_map_tiles',
value: @count
}
PrometheusExporter::Client.default.send_json(metric_data)
rescue StandardError => e
Rails.logger.error("Failed to send tile usage metric: #{e.message}")
end
end

View File

@@ -53,6 +53,7 @@
data-coordinates="<%= @coordinates %>"
data-distance="<%= @distance %>"
data-points_number="<%= @points_number %>"
data-maps-monitoring-enabled-value="<%= DawarichSettings.prometheus_exporter_enabled? %>"
data-timezone="<%= Rails.configuration.time_zone %>">
<div data-maps-target="container" class="h-[25rem] rounded-lg w-full min-h-screen">
<div id="fog" class="fog"></div>

View File

@@ -3,10 +3,25 @@
<div class="min-h-content w-full my-5">
<%= render 'settings/navigation' %>
<div class="flex justify-between items-center mt-5">
<div class="flex justify-between items-center my-5">
<h1 class="font-bold text-4xl">Maps settings</h1>
</div>
<div role="alert" class="alert alert-info">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
class="h-6 w-6 shrink-0 stroke-current">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span>Please remember, that using a custom tile URL may result in extra costs. Check your map tile provider's terms of service for more information.</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-5" data-controller="map-preview">
<%= form_for :maps,
url: settings_maps_path,

View File

@@ -18,12 +18,11 @@ class DawarichSettings
@geoapify_enabled ||= GEOAPIFY_API_KEY.present?
end
def meters_between_tracks
@meters_between_tracks ||= 300
end
def minutes_between_tracks
@minutes_between_tracks ||= 20
def prometheus_exporter_enabled?
@prometheus_exporter_enabled ||=
ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true' &&
ENV['PROMETHEUS_EXPORTER_HOST'].present? &&
ENV['PROMETHEUS_EXPORTER_PORT'].present?
end
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
if !Rails.env.test? && ENV['PROMETHEUS_EXPORTER_ENABLED'].to_s == 'true'
if !Rails.env.test? && DawarichSettings.prometheus_exporter_enabled?
require 'prometheus_exporter/middleware'
require 'prometheus_exporter/instrumentation'

View File

@@ -96,6 +96,8 @@ Rails.application.routes.draw do
get 'thumbnail', constraints: { id: %r{[^/]+} }
end
end
resources :tile_usages, only: [:create]
end
end
end