mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-27 18:12:29 +08:00
Merge pull request #2024 from TriliumNext/feat/metrics-endpoint
Metrics, metrics endpoint, and showing cool data
This commit is contained in:
commit
39f1c4e57d
43
_regroup/test-etapi/api-metrics.http
Normal file
43
_regroup/test-etapi/api-metrics.http
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
### Test regular API metrics endpoint (requires session authentication)
|
||||||
|
|
||||||
|
### Get metrics from regular API (default Prometheus format)
|
||||||
|
GET {{triliumHost}}/api/metrics
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("API metrics endpoint returns Prometheus format by default", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
|
||||||
|
client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in JSON format
|
||||||
|
GET {{triliumHost}}/api/metrics?format=json
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("API metrics endpoint returns JSON when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
|
||||||
|
client.assert(response.body.version, "Version info not present");
|
||||||
|
client.assert(response.body.database, "Database info not present");
|
||||||
|
client.assert(response.body.timestamp, "Timestamp not present");
|
||||||
|
client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
|
||||||
|
client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
|
||||||
|
client.assert(response.body.noteTypes, "Note types breakdown not present");
|
||||||
|
client.assert(response.body.attachmentTypes, "Attachment types breakdown not present");
|
||||||
|
client.assert(response.body.statistics, "Statistics not present");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test invalid format parameter
|
||||||
|
GET {{triliumHost}}/api/metrics?format=xml
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Invalid format parameter returns error", function() {
|
||||||
|
client.assert(response.status === 500, "Response status should be 500");
|
||||||
|
client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
|
||||||
|
});
|
||||||
|
%}
|
82
_regroup/test-etapi/metrics.http
Normal file
82
_regroup/test-etapi/metrics.http
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
### Test ETAPI metrics endpoint
|
||||||
|
|
||||||
|
# First login to get a token
|
||||||
|
POST {{triliumHost}}/etapi/auth/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "{{password}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Login successful", function() {
|
||||||
|
client.assert(response.status === 201, "Response status is not 201");
|
||||||
|
client.assert(response.body.authToken, "Auth token not present");
|
||||||
|
client.global.set("authToken", response.body.authToken);
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics with authentication (default Prometheus format)
|
||||||
|
GET {{triliumHost}}/etapi/metrics
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns Prometheus format by default", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
client.assert(response.body.includes("# HELP"), "Should contain HELP comments");
|
||||||
|
client.assert(response.body.includes("# TYPE"), "Should contain TYPE comments");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in JSON format
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=json
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns JSON when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("application/json"), "Content-Type should be application/json");
|
||||||
|
client.assert(response.body.version, "Version info not present");
|
||||||
|
client.assert(response.body.database, "Database info not present");
|
||||||
|
client.assert(response.body.timestamp, "Timestamp not present");
|
||||||
|
client.assert(typeof response.body.database.totalNotes === 'number', "Total notes should be a number");
|
||||||
|
client.assert(typeof response.body.database.activeNotes === 'number', "Active notes should be a number");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Get metrics in Prometheus format explicitly
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=prometheus
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint returns Prometheus format when requested", function() {
|
||||||
|
client.assert(response.status === 200, "Response status is not 200");
|
||||||
|
client.assert(response.headers["content-type"].includes("text/plain"), "Content-Type should be text/plain");
|
||||||
|
client.assert(response.body.includes("trilium_info"), "Should contain trilium_info metric");
|
||||||
|
client.assert(response.body.includes("trilium_notes_total"), "Should contain trilium_notes_total metric");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test invalid format parameter
|
||||||
|
GET {{triliumHost}}/etapi/metrics?format=xml
|
||||||
|
Authorization: {{authToken}}
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Invalid format parameter returns error", function() {
|
||||||
|
client.assert(response.status === 400, "Response status should be 400");
|
||||||
|
client.assert(response.body.code === "INVALID_FORMAT", "Error code should be INVALID_FORMAT");
|
||||||
|
client.assert(response.body.message.includes("prometheus"), "Error message should mention supported formats");
|
||||||
|
});
|
||||||
|
%}
|
||||||
|
|
||||||
|
### Test without authentication (should fail)
|
||||||
|
GET {{triliumHost}}/etapi/metrics
|
||||||
|
|
||||||
|
> {%
|
||||||
|
client.test("Metrics endpoint requires authentication", function() {
|
||||||
|
client.assert(response.status === 401, "Response status should be 401");
|
||||||
|
});
|
||||||
|
%}
|
File diff suppressed because one or more lines are too long
@ -275,9 +275,9 @@
|
|||||||
content via the injected <code>now</code> and <code>parentNote</code> variables.</p>
|
content via the injected <code>now</code> and <code>parentNote</code> variables.</p>
|
||||||
<p>Examples:</p>
|
<p>Examples:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>${parentNote.getLabel('authorName')}'s literary works</code>
|
<li><code><span class="math-tex">\({parentNote.getLabel('authorName')}'s literary works</span></code>
|
||||||
</li>
|
</li>
|
||||||
<li><code>Log for ${now.format('YYYY-MM-DD HH:mm:ss')}</code>
|
<li><code>Log for \){now.format('YYYY-MM-DD HH:mm:ss')}</code>
|
||||||
</li>
|
</li>
|
||||||
<li>to mirror the parent's template.</li>
|
<li>to mirror the parent's template.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
88
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics.html
generated
Normal file
88
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Advanced Usage/Metrics.html
generated
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<p> </p>
|
||||||
|
<h1><strong>Trilium Metrics API</strong></h1>
|
||||||
|
<p>The Trilium metrics API provides comprehensive monitoring data about your
|
||||||
|
Trilium instance, designed for external monitoring systems like Prometheus.</p>
|
||||||
|
<h2><strong>Endpoint</strong></h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>URL</strong>: <code>/etapi/metrics</code>
|
||||||
|
</li>
|
||||||
|
<li><strong>Method</strong>: <code>GET</code>
|
||||||
|
</li>
|
||||||
|
<li><strong>Authentication</strong>: ETAPI token required</li>
|
||||||
|
<li><strong>Default Format</strong>: Prometheus text format</li>
|
||||||
|
</ul>
|
||||||
|
<h2><strong>Authentication</strong></h2>
|
||||||
|
<p>You need an ETAPI token to access the metrics endpoint. Get one by:</p><pre><code class="language-text-x-trilium-auto"># Get an ETAPI token
|
||||||
|
curl -X POST http://localhost:8080/etapi/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"password": "your_password"}'
|
||||||
|
</code></pre>
|
||||||
|
<h2><strong>Usage</strong></h2>
|
||||||
|
<h3><strong>Prometheus Format (Default)</strong></h3><pre><code class="language-text-x-trilium-auto">curl -H "Authorization: YOUR_ETAPI_TOKEN" \
|
||||||
|
http://localhost:8080/etapi/metrics
|
||||||
|
</code></pre>
|
||||||
|
<p>Returns metrics in Prometheus text format:</p><pre><code class="language-text-x-trilium-auto"># HELP trilium_info Trilium instance information
|
||||||
|
# TYPE trilium_info gauge
|
||||||
|
trilium_info{version="0.91.6",db_version="231",node_version="v18.17.0"} 1 1701432000
|
||||||
|
|
||||||
|
# HELP trilium_notes_total Total number of notes including deleted
|
||||||
|
# TYPE trilium_notes_total gauge
|
||||||
|
trilium_notes_total 1234 1701432000
|
||||||
|
</code></pre>
|
||||||
|
<h3><strong>JSON Format</strong></h3><pre><code class="language-text-x-trilium-auto">curl -H "Authorization: YOUR_ETAPI_TOKEN" \
|
||||||
|
"http://localhost:8080/etapi/metrics?format=json"
|
||||||
|
</code></pre>
|
||||||
|
<p>Returns detailed metrics in JSON format for debugging or custom integrations.</p>
|
||||||
|
<h2><strong>Available Metrics</strong></h2>
|
||||||
|
<h3><strong>Instance Information</strong></h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>trilium_info</code> - Version and build information with labels</li>
|
||||||
|
</ul>
|
||||||
|
<h3><strong>Database Metrics</strong></h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>trilium_notes_total</code> - Total notes (including deleted)</li>
|
||||||
|
<li><code>trilium_notes_deleted</code> - Number of deleted notes</li>
|
||||||
|
<li><code>trilium_notes_active</code> - Number of active notes</li>
|
||||||
|
<li><code>trilium_notes_protected</code> - Number of protected notes</li>
|
||||||
|
<li><code>trilium_attachments_total</code> - Total attachments</li>
|
||||||
|
<li><code>trilium_attachments_active</code> - Active attachments</li>
|
||||||
|
<li><code>trilium_revisions_total</code> - Total note revisions</li>
|
||||||
|
<li><code>trilium_branches_total</code> - Active branches</li>
|
||||||
|
<li><code>trilium_attributes_total</code> - Active attributes</li>
|
||||||
|
<li><code>trilium_blobs_total</code> - Total blob records</li>
|
||||||
|
<li><code>trilium_etapi_tokens_total</code> - Active ETAPI tokens</li>
|
||||||
|
<li><code>trilium_embeddings_total</code> - Note embeddings (if available)</li>
|
||||||
|
</ul>
|
||||||
|
<h3><strong>Categorized Metrics</strong></h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>trilium_notes_by_type{type="text|code|image|file"}</code> - Notes
|
||||||
|
by type</li>
|
||||||
|
<li><code>trilium_attachments_by_type{mime_type="..."}</code> - Attachments
|
||||||
|
by MIME type</li>
|
||||||
|
</ul>
|
||||||
|
<h3><strong>Statistics</strong></h3>
|
||||||
|
<ul>
|
||||||
|
<li><code>trilium_database_size_bytes</code> - Database size in bytes</li>
|
||||||
|
<li><code>trilium_oldest_note_timestamp</code> - Timestamp of oldest note</li>
|
||||||
|
<li><code>trilium_newest_note_timestamp</code> - Timestamp of newest note</li>
|
||||||
|
<li><code>trilium_last_modified_timestamp</code> - Last modification timestamp</li>
|
||||||
|
</ul>
|
||||||
|
<h2><strong>Prometheus Configuration</strong></h2>
|
||||||
|
<p>Add to your <code>prometheus.yml</code>:</p><pre><code class="language-text-x-trilium-auto">scrape_configs:
|
||||||
|
- job_name: 'trilium'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:8080']
|
||||||
|
metrics_path: '/etapi/metrics'
|
||||||
|
headers:
|
||||||
|
Authorization: 'YOUR_ETAPI_TOKEN'
|
||||||
|
scrape_interval: 30s
|
||||||
|
</code></pre>
|
||||||
|
<h2><strong>Error Responses</strong></h2>
|
||||||
|
<ul>
|
||||||
|
<li><code>400</code> - Invalid format parameter</li>
|
||||||
|
<li><code>401</code> - Missing or invalid ETAPI token</li>
|
||||||
|
<li><code>500</code> - Internal server error</li>
|
||||||
|
</ul>
|
||||||
|
<p> </p>
|
||||||
|
<p> </p>
|
||||||
|
<p> </p>
|
268
apps/server/src/etapi/metrics.ts
Normal file
268
apps/server/src/etapi/metrics.ts
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
import type { Router, Request, Response, NextFunction } from "express";
|
||||||
|
import eu from "./etapi_utils.js";
|
||||||
|
import sql from "../services/sql.js";
|
||||||
|
import appInfo from "../services/app_info.js";
|
||||||
|
|
||||||
|
interface MetricsData {
|
||||||
|
version: {
|
||||||
|
app: string;
|
||||||
|
db: number;
|
||||||
|
node: string;
|
||||||
|
sync: number;
|
||||||
|
buildDate: string;
|
||||||
|
buildRevision: string;
|
||||||
|
};
|
||||||
|
database: {
|
||||||
|
totalNotes: number;
|
||||||
|
deletedNotes: number;
|
||||||
|
activeNotes: number;
|
||||||
|
protectedNotes: number;
|
||||||
|
totalAttachments: number;
|
||||||
|
deletedAttachments: number;
|
||||||
|
activeAttachments: number;
|
||||||
|
totalRevisions: number;
|
||||||
|
totalBranches: number;
|
||||||
|
totalAttributes: number;
|
||||||
|
totalBlobs: number;
|
||||||
|
totalEtapiTokens: number;
|
||||||
|
totalRecentNotes: number;
|
||||||
|
totalEmbeddings: number;
|
||||||
|
totalEmbeddingProviders: number;
|
||||||
|
};
|
||||||
|
noteTypes: Record<string, number>;
|
||||||
|
attachmentTypes: Record<string, number>;
|
||||||
|
statistics: {
|
||||||
|
oldestNote: string | null;
|
||||||
|
newestNote: string | null;
|
||||||
|
lastModified: string | null;
|
||||||
|
databaseSizeBytes: number | null;
|
||||||
|
};
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts metrics data to Prometheus text format
|
||||||
|
*/
|
||||||
|
function formatPrometheusMetrics(data: MetricsData): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const timestamp = Math.floor(new Date(data.timestamp).getTime() / 1000);
|
||||||
|
|
||||||
|
// Helper function to add a metric
|
||||||
|
const addMetric = (name: string, value: number | null, help: string, type: string = 'gauge', labels: Record<string, string> = {}) => {
|
||||||
|
if (value === null) return;
|
||||||
|
|
||||||
|
lines.push(`# HELP ${name} ${help}`);
|
||||||
|
lines.push(`# TYPE ${name} ${type}`);
|
||||||
|
|
||||||
|
const labelStr = Object.entries(labels).length > 0
|
||||||
|
? `{${Object.entries(labels).map(([k, v]) => `${k}="${v}"`).join(',')}}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
lines.push(`${name}${labelStr} ${value} ${timestamp}`);
|
||||||
|
lines.push('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Version info
|
||||||
|
addMetric('trilium_info', 1, 'Trilium instance information', 'gauge', {
|
||||||
|
version: data.version.app,
|
||||||
|
db_version: data.version.db.toString(),
|
||||||
|
node_version: data.version.node,
|
||||||
|
sync_version: data.version.sync.toString(),
|
||||||
|
build_date: data.version.buildDate,
|
||||||
|
build_revision: data.version.buildRevision
|
||||||
|
});
|
||||||
|
|
||||||
|
// Database metrics
|
||||||
|
addMetric('trilium_notes_total', data.database.totalNotes, 'Total number of notes including deleted');
|
||||||
|
addMetric('trilium_notes_deleted', data.database.deletedNotes, 'Number of deleted notes');
|
||||||
|
addMetric('trilium_notes_active', data.database.activeNotes, 'Number of active notes');
|
||||||
|
addMetric('trilium_notes_protected', data.database.protectedNotes, 'Number of protected notes');
|
||||||
|
|
||||||
|
addMetric('trilium_attachments_total', data.database.totalAttachments, 'Total number of attachments including deleted');
|
||||||
|
addMetric('trilium_attachments_deleted', data.database.deletedAttachments, 'Number of deleted attachments');
|
||||||
|
addMetric('trilium_attachments_active', data.database.activeAttachments, 'Number of active attachments');
|
||||||
|
|
||||||
|
addMetric('trilium_revisions_total', data.database.totalRevisions, 'Total number of note revisions');
|
||||||
|
addMetric('trilium_branches_total', data.database.totalBranches, 'Number of active branches');
|
||||||
|
addMetric('trilium_attributes_total', data.database.totalAttributes, 'Number of active attributes');
|
||||||
|
addMetric('trilium_blobs_total', data.database.totalBlobs, 'Total number of blob records');
|
||||||
|
addMetric('trilium_etapi_tokens_total', data.database.totalEtapiTokens, 'Number of active ETAPI tokens');
|
||||||
|
addMetric('trilium_recent_notes_total', data.database.totalRecentNotes, 'Number of recent notes tracked');
|
||||||
|
addMetric('trilium_embeddings_total', data.database.totalEmbeddings, 'Number of note embeddings');
|
||||||
|
addMetric('trilium_embedding_providers_total', data.database.totalEmbeddingProviders, 'Number of embedding providers');
|
||||||
|
|
||||||
|
// Note types
|
||||||
|
for (const [type, count] of Object.entries(data.noteTypes)) {
|
||||||
|
addMetric('trilium_notes_by_type', count, 'Number of notes by type', 'gauge', { type });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment types
|
||||||
|
for (const [mime, count] of Object.entries(data.attachmentTypes)) {
|
||||||
|
addMetric('trilium_attachments_by_type', count, 'Number of attachments by MIME type', 'gauge', { mime_type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
if (data.statistics.databaseSizeBytes !== null) {
|
||||||
|
addMetric('trilium_database_size_bytes', data.statistics.databaseSizeBytes, 'Database size in bytes');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.statistics.oldestNote) {
|
||||||
|
const oldestTimestamp = Math.floor(new Date(data.statistics.oldestNote).getTime() / 1000);
|
||||||
|
addMetric('trilium_oldest_note_timestamp', oldestTimestamp, 'Timestamp of the oldest note');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.statistics.newestNote) {
|
||||||
|
const newestTimestamp = Math.floor(new Date(data.statistics.newestNote).getTime() / 1000);
|
||||||
|
addMetric('trilium_newest_note_timestamp', newestTimestamp, 'Timestamp of the newest note');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.statistics.lastModified) {
|
||||||
|
const lastModifiedTimestamp = Math.floor(new Date(data.statistics.lastModified).getTime() / 1000);
|
||||||
|
addMetric('trilium_last_modified_timestamp', lastModifiedTimestamp, 'Timestamp of the last modification');
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects comprehensive metrics about the Trilium instance
|
||||||
|
*/
|
||||||
|
function collectMetrics(): MetricsData {
|
||||||
|
// Version information
|
||||||
|
const version = {
|
||||||
|
app: appInfo.appVersion,
|
||||||
|
db: appInfo.dbVersion,
|
||||||
|
node: appInfo.nodeVersion,
|
||||||
|
sync: appInfo.syncVersion,
|
||||||
|
buildDate: appInfo.buildDate,
|
||||||
|
buildRevision: appInfo.buildRevision
|
||||||
|
};
|
||||||
|
|
||||||
|
// Database counts
|
||||||
|
const totalNotes = sql.getValue<number>("SELECT COUNT(*) FROM notes");
|
||||||
|
const deletedNotes = sql.getValue<number>("SELECT COUNT(*) FROM notes WHERE isDeleted = 1");
|
||||||
|
const activeNotes = totalNotes - deletedNotes;
|
||||||
|
const protectedNotes = sql.getValue<number>("SELECT COUNT(*) FROM notes WHERE isProtected = 1 AND isDeleted = 0");
|
||||||
|
|
||||||
|
const totalAttachments = sql.getValue<number>("SELECT COUNT(*) FROM attachments");
|
||||||
|
const deletedAttachments = sql.getValue<number>("SELECT COUNT(*) FROM attachments WHERE isDeleted = 1");
|
||||||
|
const activeAttachments = totalAttachments - deletedAttachments;
|
||||||
|
|
||||||
|
const totalRevisions = sql.getValue<number>("SELECT COUNT(*) FROM revisions");
|
||||||
|
const totalBranches = sql.getValue<number>("SELECT COUNT(*) FROM branches WHERE isDeleted = 0");
|
||||||
|
const totalAttributes = sql.getValue<number>("SELECT COUNT(*) FROM attributes WHERE isDeleted = 0");
|
||||||
|
const totalBlobs = sql.getValue<number>("SELECT COUNT(*) FROM blobs");
|
||||||
|
const totalEtapiTokens = sql.getValue<number>("SELECT COUNT(*) FROM etapi_tokens WHERE isDeleted = 0");
|
||||||
|
const totalRecentNotes = sql.getValue<number>("SELECT COUNT(*) FROM recent_notes");
|
||||||
|
|
||||||
|
// Embedding-related metrics (these tables might not exist in older versions)
|
||||||
|
let totalEmbeddings = 0;
|
||||||
|
let totalEmbeddingProviders = 0;
|
||||||
|
try {
|
||||||
|
totalEmbeddings = sql.getValue<number>("SELECT COUNT(*) FROM note_embeddings");
|
||||||
|
totalEmbeddingProviders = sql.getValue<number>("SELECT COUNT(*) FROM embedding_providers");
|
||||||
|
} catch (e) {
|
||||||
|
// Tables don't exist, keep defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
const database = {
|
||||||
|
totalNotes,
|
||||||
|
deletedNotes,
|
||||||
|
activeNotes,
|
||||||
|
protectedNotes,
|
||||||
|
totalAttachments,
|
||||||
|
deletedAttachments,
|
||||||
|
activeAttachments,
|
||||||
|
totalRevisions,
|
||||||
|
totalBranches,
|
||||||
|
totalAttributes,
|
||||||
|
totalBlobs,
|
||||||
|
totalEtapiTokens,
|
||||||
|
totalRecentNotes,
|
||||||
|
totalEmbeddings,
|
||||||
|
totalEmbeddingProviders
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note types breakdown
|
||||||
|
const noteTypesRows = sql.getRows<{ type: string; count: number }>(
|
||||||
|
"SELECT type, COUNT(*) as count FROM notes WHERE isDeleted = 0 GROUP BY type ORDER BY count DESC"
|
||||||
|
);
|
||||||
|
const noteTypes: Record<string, number> = {};
|
||||||
|
for (const row of noteTypesRows) {
|
||||||
|
noteTypes[row.type] = row.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachment types breakdown
|
||||||
|
const attachmentTypesRows = sql.getRows<{ mime: string; count: number }>(
|
||||||
|
"SELECT mime, COUNT(*) as count FROM attachments WHERE isDeleted = 0 GROUP BY mime ORDER BY count DESC"
|
||||||
|
);
|
||||||
|
const attachmentTypes: Record<string, number> = {};
|
||||||
|
for (const row of attachmentTypesRows) {
|
||||||
|
attachmentTypes[row.mime] = row.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
const oldestNote = sql.getValue<string | null>(
|
||||||
|
"SELECT utcDateCreated FROM notes WHERE isDeleted = 0 ORDER BY utcDateCreated ASC LIMIT 1"
|
||||||
|
);
|
||||||
|
const newestNote = sql.getValue<string | null>(
|
||||||
|
"SELECT utcDateCreated FROM notes WHERE isDeleted = 0 ORDER BY utcDateCreated DESC LIMIT 1"
|
||||||
|
);
|
||||||
|
const lastModified = sql.getValue<string | null>(
|
||||||
|
"SELECT utcDateModified FROM notes WHERE isDeleted = 0 ORDER BY utcDateModified DESC LIMIT 1"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Database size (this might not work on all systems)
|
||||||
|
let databaseSizeBytes: number | null = null;
|
||||||
|
try {
|
||||||
|
const sizeResult = sql.getValue<number>("SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size()");
|
||||||
|
databaseSizeBytes = sizeResult;
|
||||||
|
} catch (e) {
|
||||||
|
// Pragma might not be available
|
||||||
|
}
|
||||||
|
|
||||||
|
const statistics = {
|
||||||
|
oldestNote,
|
||||||
|
newestNote,
|
||||||
|
lastModified,
|
||||||
|
databaseSizeBytes
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
database,
|
||||||
|
noteTypes,
|
||||||
|
attachmentTypes,
|
||||||
|
statistics,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(router: Router): void {
|
||||||
|
eu.route(router, "get", "/etapi/metrics", (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const metrics = collectMetrics();
|
||||||
|
const format = (req.query.format as string)?.toLowerCase() || 'prometheus';
|
||||||
|
|
||||||
|
if (format === 'json') {
|
||||||
|
res.status(200).json(metrics);
|
||||||
|
} else if (format === 'prometheus') {
|
||||||
|
const prometheusText = formatPrometheusMetrics(metrics);
|
||||||
|
res.status(200)
|
||||||
|
.set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
|
||||||
|
.send(prometheusText);
|
||||||
|
} else {
|
||||||
|
throw new eu.EtapiError(400, "INVALID_FORMAT", "Supported formats: 'prometheus' (default), 'json'");
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
|
throw new eu.EtapiError(500, "METRICS_ERROR", `Failed to collect metrics: ${errorMessage}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
register,
|
||||||
|
collectMetrics,
|
||||||
|
formatPrometheusMetrics
|
||||||
|
};
|
171
apps/server/src/routes/api/metrics.ts
Normal file
171
apps/server/src/routes/api/metrics.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import type { Request, Response } from "express";
|
||||||
|
import etapiMetrics from "../../etapi/metrics.js";
|
||||||
|
|
||||||
|
type MetricsData = ReturnType<typeof etapiMetrics.collectMetrics>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/metrics:
|
||||||
|
* get:
|
||||||
|
* summary: Get Trilium instance metrics
|
||||||
|
* operationId: metrics
|
||||||
|
* parameters:
|
||||||
|
* - in: query
|
||||||
|
* name: format
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* enum: [prometheus, json]
|
||||||
|
* default: prometheus
|
||||||
|
* description: Response format - 'prometheus' (default) for Prometheus text format, 'json' for JSON
|
||||||
|
* responses:
|
||||||
|
* '200':
|
||||||
|
* description: Instance metrics
|
||||||
|
* content:
|
||||||
|
* text/plain:
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* example: |
|
||||||
|
* # HELP trilium_info Trilium instance information
|
||||||
|
* # TYPE trilium_info gauge
|
||||||
|
* trilium_info{version="0.91.6",db_version="231",node_version="v18.17.0"} 1 1701432000
|
||||||
|
*
|
||||||
|
* # HELP trilium_notes_total Total number of notes including deleted
|
||||||
|
* # TYPE trilium_notes_total gauge
|
||||||
|
* trilium_notes_total 1234 1701432000
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* version:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* app:
|
||||||
|
* type: string
|
||||||
|
* example: "0.91.6"
|
||||||
|
* db:
|
||||||
|
* type: integer
|
||||||
|
* example: 231
|
||||||
|
* node:
|
||||||
|
* type: string
|
||||||
|
* example: "v18.17.0"
|
||||||
|
* sync:
|
||||||
|
* type: integer
|
||||||
|
* example: 35
|
||||||
|
* buildDate:
|
||||||
|
* type: string
|
||||||
|
* example: "2024-09-07T18:36:34Z"
|
||||||
|
* buildRevision:
|
||||||
|
* type: string
|
||||||
|
* example: "7c0d6930fa8f20d269dcfbcbc8f636a25f6bb9a7"
|
||||||
|
* database:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* totalNotes:
|
||||||
|
* type: integer
|
||||||
|
* example: 1234
|
||||||
|
* deletedNotes:
|
||||||
|
* type: integer
|
||||||
|
* example: 56
|
||||||
|
* activeNotes:
|
||||||
|
* type: integer
|
||||||
|
* example: 1178
|
||||||
|
* protectedNotes:
|
||||||
|
* type: integer
|
||||||
|
* example: 23
|
||||||
|
* totalAttachments:
|
||||||
|
* type: integer
|
||||||
|
* example: 89
|
||||||
|
* deletedAttachments:
|
||||||
|
* type: integer
|
||||||
|
* example: 5
|
||||||
|
* activeAttachments:
|
||||||
|
* type: integer
|
||||||
|
* example: 84
|
||||||
|
* totalRevisions:
|
||||||
|
* type: integer
|
||||||
|
* example: 567
|
||||||
|
* totalBranches:
|
||||||
|
* type: integer
|
||||||
|
* example: 1200
|
||||||
|
* totalAttributes:
|
||||||
|
* type: integer
|
||||||
|
* example: 345
|
||||||
|
* totalBlobs:
|
||||||
|
* type: integer
|
||||||
|
* example: 678
|
||||||
|
* totalEtapiTokens:
|
||||||
|
* type: integer
|
||||||
|
* example: 3
|
||||||
|
* totalRecentNotes:
|
||||||
|
* type: integer
|
||||||
|
* example: 50
|
||||||
|
* totalEmbeddings:
|
||||||
|
* type: integer
|
||||||
|
* example: 123
|
||||||
|
* totalEmbeddingProviders:
|
||||||
|
* type: integer
|
||||||
|
* example: 2
|
||||||
|
* noteTypes:
|
||||||
|
* type: object
|
||||||
|
* additionalProperties:
|
||||||
|
* type: integer
|
||||||
|
* example:
|
||||||
|
* text: 800
|
||||||
|
* code: 200
|
||||||
|
* image: 100
|
||||||
|
* file: 50
|
||||||
|
* attachmentTypes:
|
||||||
|
* type: object
|
||||||
|
* additionalProperties:
|
||||||
|
* type: integer
|
||||||
|
* example:
|
||||||
|
* "image/png": 45
|
||||||
|
* "image/jpeg": 30
|
||||||
|
* "application/pdf": 14
|
||||||
|
* statistics:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* oldestNote:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* example: "2020-01-01T00:00:00.000Z"
|
||||||
|
* newestNote:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* example: "2024-12-01T12:00:00.000Z"
|
||||||
|
* lastModified:
|
||||||
|
* type: string
|
||||||
|
* nullable: true
|
||||||
|
* example: "2024-12-01T11:30:00.000Z"
|
||||||
|
* databaseSizeBytes:
|
||||||
|
* type: integer
|
||||||
|
* nullable: true
|
||||||
|
* example: 52428800
|
||||||
|
* timestamp:
|
||||||
|
* type: string
|
||||||
|
* example: "2024-12-01T12:00:00.000Z"
|
||||||
|
* '400':
|
||||||
|
* description: Invalid format parameter
|
||||||
|
* '500':
|
||||||
|
* description: Error collecting metrics
|
||||||
|
* security:
|
||||||
|
* - session: []
|
||||||
|
*/
|
||||||
|
function getMetrics(req: Request, res: Response): string | MetricsData {
|
||||||
|
const format = (req.query?.format as string)?.toLowerCase() || 'prometheus';
|
||||||
|
|
||||||
|
if (format === 'json') {
|
||||||
|
return etapiMetrics.collectMetrics();
|
||||||
|
} else if (format === 'prometheus') {
|
||||||
|
const metrics = etapiMetrics.collectMetrics();
|
||||||
|
const prometheusText = etapiMetrics.formatPrometheusMetrics(metrics);
|
||||||
|
res.set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8');
|
||||||
|
return prometheusText;
|
||||||
|
} else {
|
||||||
|
throw new Error("Supported formats: 'prometheus' (default), 'json'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getMetrics
|
||||||
|
};
|
@ -52,6 +52,7 @@ import fontsRoute from "./api/fonts.js";
|
|||||||
import etapiTokensApiRoutes from "./api/etapi_tokens.js";
|
import etapiTokensApiRoutes from "./api/etapi_tokens.js";
|
||||||
import relationMapApiRoute from "./api/relation-map.js";
|
import relationMapApiRoute from "./api/relation-map.js";
|
||||||
import otherRoute from "./api/other.js";
|
import otherRoute from "./api/other.js";
|
||||||
|
import metricsRoute from "./api/metrics.js";
|
||||||
import shareRoutes from "../share/routes.js";
|
import shareRoutes from "../share/routes.js";
|
||||||
import embeddingsRoute from "./api/embeddings.js";
|
import embeddingsRoute from "./api/embeddings.js";
|
||||||
import ollamaRoute from "./api/ollama.js";
|
import ollamaRoute from "./api/ollama.js";
|
||||||
@ -68,6 +69,7 @@ import etapiNoteRoutes from "../etapi/notes.js";
|
|||||||
import etapiSpecialNoteRoutes from "../etapi/special_notes.js";
|
import etapiSpecialNoteRoutes from "../etapi/special_notes.js";
|
||||||
import etapiSpecRoute from "../etapi/spec.js";
|
import etapiSpecRoute from "../etapi/spec.js";
|
||||||
import etapiBackupRoute from "../etapi/backup.js";
|
import etapiBackupRoute from "../etapi/backup.js";
|
||||||
|
import etapiMetricsRoute from "../etapi/metrics.js";
|
||||||
import apiDocsRoute from "./api_docs.js";
|
import apiDocsRoute from "./api_docs.js";
|
||||||
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
|
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
|
||||||
|
|
||||||
@ -236,6 +238,7 @@ function register(app: express.Application) {
|
|||||||
|
|
||||||
apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote);
|
apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote);
|
||||||
apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo);
|
apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo);
|
||||||
|
apiRoute(GET, "/api/metrics", metricsRoute.getMetrics);
|
||||||
|
|
||||||
// docker health check
|
// docker health check
|
||||||
route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler);
|
route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler);
|
||||||
@ -363,6 +366,7 @@ function register(app: express.Application) {
|
|||||||
etapiSpecialNoteRoutes.register(router);
|
etapiSpecialNoteRoutes.register(router);
|
||||||
etapiSpecRoute.register(router);
|
etapiSpecRoute.register(router);
|
||||||
etapiBackupRoute.register(router);
|
etapiBackupRoute.register(router);
|
||||||
|
etapiMetricsRoute.register(router);
|
||||||
|
|
||||||
// LLM Chat API
|
// LLM Chat API
|
||||||
asyncApiRoute(PST, "/api/llm/chat", llmRoute.createSession);
|
asyncApiRoute(PST, "/api/llm/chat", llmRoute.createSession);
|
||||||
|
@ -1735,37 +1735,30 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_optionsAppearance",
|
"value": "nRhnJkTT8cPs",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 180
|
"position": 180
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "nRhnJkTT8cPs",
|
"value": "KSZ04uQ2D1St",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 190
|
"position": 190
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "KSZ04uQ2D1St",
|
"value": "WOcw2SLH6tbX",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 200
|
"position": 200
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "WOcw2SLH6tbX",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 210
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "veGu4faJErEM",
|
"value": "veGu4faJErEM",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 220
|
"position": 210
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -2053,30 +2046,23 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_options",
|
"value": "oPVyFC7WL2Lp",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "oPVyFC7WL2Lp",
|
"value": "3seOhtN8uLIY",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "3seOhtN8uLIY",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 50
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "cbkrhQjrkKrh",
|
"value": "cbkrhQjrkKrh",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -3076,65 +3062,51 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_optionsTextNotes",
|
"value": "zEY4DaJG4YT5",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_optionsCodeNotes",
|
"value": "iPIMuisry3hd",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "zEY4DaJG4YT5",
|
"value": "6f9hih2hXXZk",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "iPIMuisry3hd",
|
"value": "4TIF1oA4VQRO",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "6f9hih2hXXZk",
|
"value": "BlN9DFI679QC",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "4TIF1oA4VQRO",
|
"value": "XpOYSgsLkTJy",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 60
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "BlN9DFI679QC",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 70
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "XpOYSgsLkTJy",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 80
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "s1aBHPd79XYj",
|
"value": "s1aBHPd79XYj",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 70
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -5479,19 +5451,12 @@
|
|||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 10
|
"position": 10
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "_optionsTextNotes",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 20
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "BlN9DFI679QC",
|
"value": "BlN9DFI679QC",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -5668,23 +5633,16 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_optionsTextNotes",
|
"value": "zEY4DaJG4YT5",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 30
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "zEY4DaJG4YT5",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 40
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "BFvAtE74rbP6",
|
"value": "BFvAtE74rbP6",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -6694,19 +6652,12 @@
|
|||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 20
|
"position": 20
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "_optionsTextNotes",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 30
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "zEY4DaJG4YT5",
|
"value": "zEY4DaJG4YT5",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 30
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -8521,191 +8472,184 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_help_YKWqdJhzi2VY",
|
"value": "OFXdgB2nNk1F",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "OFXdgB2nNk1F",
|
"value": "BlN9DFI679QC",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "BlN9DFI679QC",
|
"value": "vZWERwf8U3nx",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 70
|
"position": 70
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "vZWERwf8U3nx",
|
"value": "oPVyFC7WL2Lp",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 80
|
"position": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "oPVyFC7WL2Lp",
|
"value": "GPERMystNGTB",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "GPERMystNGTB",
|
"value": "CoFPLs3dRlXc",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 100
|
"position": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "CoFPLs3dRlXc",
|
"value": "AlhDUqhENtH7",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 110
|
"position": 110
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "AlhDUqhENtH7",
|
"value": "pKK96zzmvBGf",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 120
|
"position": 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "pKK96zzmvBGf",
|
"value": "WFGzWeUK6arS",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 130
|
"position": 130
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "WFGzWeUK6arS",
|
"value": "0ESUbbAxVnoK",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 140
|
"position": 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "0ESUbbAxVnoK",
|
"value": "J5Ex1ZrMbyJ6",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 150
|
"position": 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "J5Ex1ZrMbyJ6",
|
"value": "d3fAXQ2diepH",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 160
|
"position": 160
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "d3fAXQ2diepH",
|
"value": "MgibgPcfeuGz",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 170
|
"position": 170
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "MgibgPcfeuGz",
|
"value": "m523cpzocqaD",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 180
|
"position": 180
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "m523cpzocqaD",
|
"value": "9sRHySam5fXb",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 190
|
"position": 190
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "9sRHySam5fXb",
|
"value": "u3YFHC9tQlpm",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 200
|
"position": 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "u3YFHC9tQlpm",
|
"value": "R9pX4DGra2Vt",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 210
|
"position": 210
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "R9pX4DGra2Vt",
|
"value": "iRwzGnHPzonm",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 220
|
"position": 220
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "iRwzGnHPzonm",
|
"value": "BCkXAVs63Ttv",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 230
|
"position": 230
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "BCkXAVs63Ttv",
|
"value": "47ZrP6FNuoG8",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 240
|
"position": 240
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "47ZrP6FNuoG8",
|
"value": "KC1HB96bqqHX",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 250
|
"position": 250
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "KC1HB96bqqHX",
|
"value": "BFvAtE74rbP6",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 260
|
"position": 260
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "BFvAtE74rbP6",
|
"value": "bdUJEHsAPYQR",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 270
|
"position": 270
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "bdUJEHsAPYQR",
|
"value": "AxshuNRegLAv",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 280
|
"position": 280
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "AxshuNRegLAv",
|
"value": "81SGnPGMk7Xc",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 290
|
"position": 290
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "81SGnPGMk7Xc",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 300
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "xWbu3jpNWapp",
|
"value": "xWbu3jpNWapp",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 310
|
"position": 300
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -10639,177 +10583,93 @@
|
|||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_globalNoteMap",
|
"value": "YKWqdJhzi2VY",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 40
|
"position": 40
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_sqlConsole",
|
"value": "ivYnonVFBxbQ",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 50
|
"position": 50
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "YKWqdJhzi2VY",
|
"value": "eIg8jdvaoNNd",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 60
|
"position": 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_search",
|
"value": "QEAPj01N5f7w",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 70
|
"position": 70
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_bulkAction",
|
"value": "m1lbrzyKDaRB",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 80
|
"position": 80
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "ivYnonVFBxbQ",
|
"value": "x3i7MxGccDuM",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 90
|
"position": 90
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_backendLog",
|
"value": "bdUJEHsAPYQR",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 100
|
"position": 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_userHidden",
|
"value": "xYmIYSP6wE3F",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 110
|
"position": 110
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_lbTplRoot",
|
"value": "u3YFHC9tQlpm",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 120
|
"position": 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_share",
|
"value": "qzNzp9LYQyPT",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 130
|
"position": 130
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_lbRoot",
|
"value": "CdNpE2pqjmI6",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 140
|
"position": 140
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "_options",
|
"value": "R9pX4DGra2Vt",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 150
|
"position": 150
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "_lbMobileRoot",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 160
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "_help",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 170
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "eIg8jdvaoNNd",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 180
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "QEAPj01N5f7w",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 190
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "m1lbrzyKDaRB",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "x3i7MxGccDuM",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 210
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "bdUJEHsAPYQR",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 220
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "xYmIYSP6wE3F",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 230
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "u3YFHC9tQlpm",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 240
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "qzNzp9LYQyPT",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 250
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "CdNpE2pqjmI6",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 260
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "relation",
|
|
||||||
"name": "internalLink",
|
|
||||||
"value": "R9pX4DGra2Vt",
|
|
||||||
"isInheritable": false,
|
|
||||||
"position": 270
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "relation",
|
"type": "relation",
|
||||||
"name": "internalLink",
|
"name": "internalLink",
|
||||||
"value": "4TIF1oA4VQRO",
|
"value": "4TIF1oA4VQRO",
|
||||||
"isInheritable": false,
|
"isInheritable": false,
|
||||||
"position": 280
|
"position": 160
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "label",
|
"type": "label",
|
||||||
@ -10831,6 +10691,33 @@
|
|||||||
"dataFileName": "Hidden Notes_image.png"
|
"dataFileName": "Hidden Notes_image.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"isClone": false,
|
||||||
|
"noteId": "uYF7pmepw27K",
|
||||||
|
"notePath": [
|
||||||
|
"pOsGYCXsbNQG",
|
||||||
|
"tC7s2alapj8V",
|
||||||
|
"uYF7pmepw27K"
|
||||||
|
],
|
||||||
|
"title": "Metrics",
|
||||||
|
"notePosition": 240,
|
||||||
|
"prefix": null,
|
||||||
|
"isExpanded": false,
|
||||||
|
"type": "text",
|
||||||
|
"mime": "text/html",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"type": "label",
|
||||||
|
"name": "iconClass",
|
||||||
|
"value": "bx bxs-data",
|
||||||
|
"isInheritable": false,
|
||||||
|
"position": 10
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"format": "markdown",
|
||||||
|
"dataFileName": "Metrics.md",
|
||||||
|
"attachments": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
File diff suppressed because one or more lines are too long
@ -27,4 +27,4 @@ The code will:
|
|||||||
1. First load the `config.ini` file as before
|
1. First load the `config.ini` file as before
|
||||||
2. Then scan all environment variables for ones starting with `TRILIUM_`
|
2. Then scan all environment variables for ones starting with `TRILIUM_`
|
||||||
3. Parse these variables into section/key pairs
|
3. Parse these variables into section/key pairs
|
||||||
4. Merge them with the config from the file, with environment variables taking precedence
|
4. Merge them with the config from the file, with environment variables taking precedence
|
110
docs/User Guide/User Guide/Advanced Usage/Metrics.md
Normal file
110
docs/User Guide/User Guide/Advanced Usage/Metrics.md
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# **Trilium Metrics API**
|
||||||
|
|
||||||
|
The Trilium metrics API provides comprehensive monitoring data about your Trilium instance, designed for external monitoring systems like Prometheus.
|
||||||
|
|
||||||
|
## **Endpoint**
|
||||||
|
|
||||||
|
* **URL**: `/etapi/metrics`
|
||||||
|
* **Method**: `GET`
|
||||||
|
* **Authentication**: ETAPI token required
|
||||||
|
* **Default Format**: Prometheus text format
|
||||||
|
|
||||||
|
## **Authentication**
|
||||||
|
|
||||||
|
You need an ETAPI token to access the metrics endpoint. Get one by:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Get an ETAPI token
|
||||||
|
curl -X POST http://localhost:8080/etapi/auth/login \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"password": "your_password"}'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## **Usage**
|
||||||
|
|
||||||
|
### **Prometheus Format (Default)**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -H "Authorization: YOUR_ETAPI_TOKEN" \
|
||||||
|
http://localhost:8080/etapi/metrics
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns metrics in Prometheus text format:
|
||||||
|
|
||||||
|
```
|
||||||
|
# HELP trilium_info Trilium instance information
|
||||||
|
# TYPE trilium_info gauge
|
||||||
|
trilium_info{version="0.91.6",db_version="231",node_version="v18.17.0"} 1 1701432000
|
||||||
|
|
||||||
|
# HELP trilium_notes_total Total number of notes including deleted
|
||||||
|
# TYPE trilium_notes_total gauge
|
||||||
|
trilium_notes_total 1234 1701432000
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### **JSON Format**
|
||||||
|
|
||||||
|
```
|
||||||
|
curl -H "Authorization: YOUR_ETAPI_TOKEN" \
|
||||||
|
"http://localhost:8080/etapi/metrics?format=json"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns detailed metrics in JSON format for debugging or custom integrations.
|
||||||
|
|
||||||
|
## **Available Metrics**
|
||||||
|
|
||||||
|
### **Instance Information**
|
||||||
|
|
||||||
|
* `trilium_info` - Version and build information with labels
|
||||||
|
|
||||||
|
### **Database Metrics**
|
||||||
|
|
||||||
|
* `trilium_notes_total` - Total notes (including deleted)
|
||||||
|
* `trilium_notes_deleted` - Number of deleted notes
|
||||||
|
* `trilium_notes_active` - Number of active notes
|
||||||
|
* `trilium_notes_protected` - Number of protected notes
|
||||||
|
* `trilium_attachments_total` - Total attachments
|
||||||
|
* `trilium_attachments_active` - Active attachments
|
||||||
|
* `trilium_revisions_total` - Total note revisions
|
||||||
|
* `trilium_branches_total` - Active branches
|
||||||
|
* `trilium_attributes_total` - Active attributes
|
||||||
|
* `trilium_blobs_total` - Total blob records
|
||||||
|
* `trilium_etapi_tokens_total` - Active ETAPI tokens
|
||||||
|
* `trilium_embeddings_total` - Note embeddings (if available)
|
||||||
|
|
||||||
|
### **Categorized Metrics**
|
||||||
|
|
||||||
|
* `trilium_notes_by_type{type="text|code|image|file"}` - Notes by type
|
||||||
|
* `trilium_attachments_by_type{mime_type="..."}` - Attachments by MIME type
|
||||||
|
|
||||||
|
### **Statistics**
|
||||||
|
|
||||||
|
* `trilium_database_size_bytes` - Database size in bytes
|
||||||
|
* `trilium_oldest_note_timestamp` - Timestamp of oldest note
|
||||||
|
* `trilium_newest_note_timestamp` - Timestamp of newest note
|
||||||
|
* `trilium_last_modified_timestamp` - Last modification timestamp
|
||||||
|
|
||||||
|
## **Prometheus Configuration**
|
||||||
|
|
||||||
|
Add to your `prometheus.yml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'trilium'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['localhost:8080']
|
||||||
|
metrics_path: '/etapi/metrics'
|
||||||
|
headers:
|
||||||
|
Authorization: 'YOUR_ETAPI_TOKEN'
|
||||||
|
scrape_interval: 30s
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## **Error Responses**
|
||||||
|
|
||||||
|
* `400` - Invalid format parameter
|
||||||
|
* `401` - Missing or invalid ETAPI token
|
||||||
|
* `500` - Internal server error
|
Loading…
x
Reference in New Issue
Block a user