mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-11-11 08:39:17 -05:00
Within metadata_ttl cached npm and Composer metadata is served without asking upstream, so a new release can take that long to show up. Both registries publish a feed of the packages that changed. upstream.npm_change_feed and upstream.composer_change_feed (off by default, require cache_metadata) start a reader per feed that runs every minute and clears fetched_at on the cached rows of listed packages that were fetched before the read. The rows keep their document and ETag, so the next request revalidates them upstream. Routed packages are cached under other keys and never match. A Packagist resync expires all cached Composer metadata. Failed reads back off up to 15 minutes. The feed position is kept in memory, so after a restart the reader starts from the feed's current position.
289 lines
15 KiB
HTML
289 lines
15 KiB
HTML
{{define "runtime_metrics"}}
|
|
<div class="bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800 mb-8">
|
|
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
<h2 class="text-lg font-semibold">Runtime</h2>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-0.5">
|
|
Counters held in this process, not in the database: they start at zero when the
|
|
proxy starts and are lost on restart. Everything above comes from the database
|
|
instead, and survives a restart — so a low count here alongside a large one
|
|
above just means the proxy started recently.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="p-6 space-y-8">
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Requests</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{.Requests}}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">In flight</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{.ActiveRequests}}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Mean latency</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{.RequestMean}}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Artifact hit rate</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{if .ArtifactCache.HasTraffic}}{{.ArtifactCache.HitRatio}}%{{else}}—{{end}}</div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Upstream fetches</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{.UpstreamFetches}}<span class="text-sm font-normal text-gray-400 dark:text-gray-500"> · {{.UpstreamFetchMean}}</span></div>
|
|
</div>
|
|
<div>
|
|
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">Served</div>
|
|
<div class="text-xl font-semibold mt-1 tabular-nums">{{.ResponseBytes}}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-8">
|
|
{{template "runtime_counts" dict "Title" "Responses by status" "Rows" .StatusClasses "Empty" "No requests yet"}}
|
|
|
|
{{template "runtime_cache" dict "Title" "Artifact cache lookups" "Lookups" .ArtifactCache}}
|
|
|
|
<div>
|
|
{{template "runtime_cache" dict "Title" "Metadata cache lookups" "Lookups" .MetadataCache}}
|
|
<dl class="space-y-2 text-sm mt-2">
|
|
<div class="flex items-center justify-between gap-4">
|
|
<dt class="text-gray-500 dark:text-gray-400">served from cache</dt>
|
|
<dd class="tabular-nums">{{.MetadataServed}}</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
|
|
{{template "runtime_stats" dict "Title" "Storage operations" "Rows" .StorageOps "Empty" "No storage operations yet"}}
|
|
{{template "runtime_counts" dict "Title" "Upstream errors" "Rows" .UpstreamErrors "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Storage errors" "Rows" .StorageErrors "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Integrity failures" "Rows" .IntegrityFailures "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Health probe failures" "Rows" .ProbeFailures "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Evicted artifacts" "Rows" .Evictions "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Evicted metadata" "Rows" .MetadataEvictions "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Metadata changed upstream" "Rows" .ChangeFeedExpired "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Circuit breaker trips" "Rows" .BreakerTrips "Empty" "None"}}
|
|
|
|
<div>
|
|
<h3 class="text-sm font-semibold mb-3">Circuit breakers</h3>
|
|
{{if .Breakers}}
|
|
<dl class="space-y-2 text-sm">
|
|
{{range .Breakers}}
|
|
<div class="flex items-center justify-between gap-4">
|
|
<dt class="truncate text-gray-500 dark:text-gray-400">{{.Registry}}</dt>
|
|
<dd>
|
|
{{if .Open}}
|
|
<span class="inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300">
|
|
<i data-lucide="alert-triangle" class="w-3 h-3"></i>{{.State}}
|
|
</span>
|
|
{{else}}
|
|
<span class="inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs font-medium bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300">
|
|
<i data-lucide="check" class="w-3 h-3"></i>{{.State}}
|
|
</span>
|
|
{{end}}
|
|
</dd>
|
|
</div>
|
|
{{end}}
|
|
</dl>
|
|
{{else}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
None reported — a breaker appears once its upstream has been fetched from.
|
|
</p>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
{{if and .HasFailures (not .FailureDetailsOn)}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Per-failure detail is off. Set <code class="text-xs">ui_failure_details: true</code>
|
|
to list the packages, URLs and errors behind each failure count here, once
|
|
<code class="text-xs">/ui</code> is behind authentication; the same detail is in the log.
|
|
</p>
|
|
{{end}}
|
|
|
|
<!-- Who is calling. Addresses are never metric labels: the caller set is
|
|
unbounded, so per-address detail lives here and in the access log. -->
|
|
<div class="border-t border-gray-200 dark:border-gray-800 pt-6">
|
|
<div class="flex flex-wrap items-baseline justify-between gap-2 mb-4">
|
|
<h3 class="text-sm font-semibold">Request sources</h3>
|
|
{{if .SourcesOn}}
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
{{if .TrustsForward}}Attributed by X-Forwarded-For{{else}}Attributed by peer address{{end}}
|
|
{{- if .SourceCount}} · {{.SourceCount}} tracked{{end}}
|
|
</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="grid lg:grid-cols-3 gap-8">
|
|
<div class="lg:col-span-2 overflow-x-auto">
|
|
{{if not .SourcesOn}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Per-caller addresses are off. Set <code class="text-xs">ui_request_sources: true</code>
|
|
to show them here, once <code class="text-xs">/ui</code> is behind authentication;
|
|
the per-request detail is in the access log.
|
|
</p>
|
|
{{else if .Sources}}
|
|
<table class="w-full text-sm">
|
|
<thead class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-800">
|
|
<tr>
|
|
<th scope="col" class="text-left font-medium pb-2">Address</th>
|
|
<th scope="col" class="text-left font-medium pb-2">Client</th>
|
|
<th scope="col" class="text-right font-medium pb-2">Requests</th>
|
|
<th scope="col" class="text-right font-medium pb-2">Downloaded</th>
|
|
<th scope="col" class="text-right font-medium pb-2">Last seen</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
|
|
{{range .Sources}}
|
|
<tr class="{{if .IsOverflow}}text-gray-500 dark:text-gray-400 italic{{end}}">
|
|
<th scope="row" class="py-2 text-left font-normal font-mono text-xs">{{.Addr}}</th>
|
|
<td class="py-2 text-left text-gray-500 dark:text-gray-400">{{.Client}}</td>
|
|
<td class="py-2 text-right tabular-nums">{{.Requests}}</td>
|
|
<td class="py-2 text-right tabular-nums font-medium">{{.Bytes}}</td>
|
|
<td class="py-2 text-right tabular-nums text-gray-500 dark:text-gray-400">{{.LastSeen}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">No requests recorded yet.</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div>
|
|
<h4 class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-3">By client</h4>
|
|
{{if .Clients}}
|
|
<table class="w-full text-sm">
|
|
<tbody>
|
|
{{range .Clients}}
|
|
<tr>
|
|
<th scope="row" class="py-1 text-left font-normal text-gray-500 dark:text-gray-400">{{.Client}}</th>
|
|
<td class="py-1 text-right tabular-nums">{{.Requests}}</td>
|
|
<td class="py-1 text-right tabular-nums font-medium">{{.Bytes}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">None yet.</p>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{if .ScanningOn}}
|
|
<div class="border-t border-gray-200 dark:border-gray-800 pt-6">
|
|
<h3 class="text-sm font-semibold mb-4">Pre-cache scanning</h3>
|
|
<div class="grid md:grid-cols-2 xl:grid-cols-3 gap-8">
|
|
{{template "runtime_stats" dict "Title" "Scans" "Rows" .Scans "Empty" "No scans yet"}}
|
|
{{template "runtime_counts" dict "Title" "Artifacts blocked" "Rows" .ScansBlocked "Empty" "None"}}
|
|
{{template "runtime_counts" dict "Title" "Scan errors" "Rows" .ScanErrors "Empty" "None"}}
|
|
</div>
|
|
</div>
|
|
{{else}}
|
|
<div class="border-t border-gray-200 dark:border-gray-800 pt-6">
|
|
<h3 class="text-sm font-semibold mb-2">Pre-cache scanning</h3>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Not configured. Enable it under <code class="text-xs">scanning</code> in the
|
|
config to have artifacts scanned before they are committed to the cache.
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* runtime_counts renders a labelled counter list, tinting failure rows. */}}
|
|
{{define "runtime_counts"}}
|
|
<div>
|
|
<h3 class="text-sm font-semibold mb-3">{{.Title}}</h3>
|
|
{{if .Rows}}
|
|
<dl class="space-y-2 text-sm">
|
|
{{range .Rows}}
|
|
<div class="flex flex-wrap items-center justify-between gap-x-4">
|
|
<dt class="min-w-0 truncate {{if .Bad}}text-red-600 dark:text-red-400{{else}}text-gray-500 dark:text-gray-400{{end}}">{{.Label}}</dt>
|
|
<dd class="shrink-0 tabular-nums {{if .Bad}}text-red-600 dark:text-red-400 font-medium{{end}}">{{.Count}}</dd>
|
|
{{if .Recent}}
|
|
<dd class="basis-full min-w-0 mt-1">
|
|
{{template "recent_failures" .Recent}}
|
|
</dd>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</dl>
|
|
{{else}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">{{.Empty}}</p>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{/* recent_failures lists the latest failures behind a failure count, newest first. */}}
|
|
{{define "recent_failures"}}
|
|
<details class="group">
|
|
<summary class="cursor-pointer text-xs text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
|
|
Latest {{len .}}
|
|
</summary>
|
|
<ul class="mt-2 space-y-2 text-xs border-l border-gray-200 dark:border-gray-800 pl-3">
|
|
{{range .}}
|
|
<li class="min-w-0">
|
|
<div class="flex items-baseline justify-between gap-2">
|
|
<span class="min-w-0 truncate font-mono">{{if .PURL}}{{.PURL}}{{else if .Path}}{{.Path}}{{else if .URL}}{{.URL}}{{else}}-{{end}}</span>
|
|
<span class="shrink-0 text-gray-400 dark:text-gray-500">{{.Age}}</span>
|
|
</div>
|
|
{{if .Filename}}<div class="truncate text-gray-500 dark:text-gray-400">{{.Filename}}</div>{{end}}
|
|
{{if and .URL (or .PURL .Path)}}<div class="truncate font-mono text-gray-500 dark:text-gray-400" title="{{.URL}}">{{.URL}}</div>{{end}}
|
|
{{if and .Path .PURL}}<div class="truncate font-mono text-gray-500 dark:text-gray-400" title="{{.Path}}">{{.Path}}</div>{{end}}
|
|
{{if .Error}}<div class="break-words text-red-600 dark:text-red-400">{{.Error}}</div>{{end}}
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
</details>
|
|
{{end}}
|
|
|
|
{{/* runtime_stats renders a histogram list: how many observations, and their mean. */}}
|
|
{{define "runtime_stats"}}
|
|
<div>
|
|
<h3 class="text-sm font-semibold mb-3">{{.Title}}</h3>
|
|
{{if .Rows}}
|
|
<table class="w-full text-sm">
|
|
<thead class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
|
<tr>
|
|
<th scope="col" class="text-left font-medium pb-2"></th>
|
|
<th scope="col" class="text-right font-medium pb-2">count</th>
|
|
<th scope="col" class="text-right font-medium pb-2">mean</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .Rows}}
|
|
<tr>
|
|
<th scope="row" class="py-1 text-left font-normal text-gray-500 dark:text-gray-400 truncate">{{.Label}}</th>
|
|
<td class="py-1 text-right tabular-nums">{{.Count}}</td>
|
|
<td class="py-1 text-right tabular-nums">{{.Mean}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
{{else}}
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">{{.Empty}}</p>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "runtime_cache"}}
|
|
<div>
|
|
<h3 class="text-sm font-semibold mb-3">{{.Title}}</h3>
|
|
<dl class="space-y-2 text-sm">
|
|
<div class="flex items-center justify-between gap-4">
|
|
<dt class="text-gray-500 dark:text-gray-400">hits</dt>
|
|
<dd class="tabular-nums">{{.Lookups.Hits}}</dd>
|
|
</div>
|
|
<div class="flex items-center justify-between gap-4">
|
|
<dt class="text-gray-500 dark:text-gray-400">misses</dt>
|
|
<dd class="tabular-nums">{{.Lookups.Misses}}</dd>
|
|
</div>
|
|
<div class="flex items-center justify-between gap-4">
|
|
<dt class="text-gray-500 dark:text-gray-400">hit rate</dt>
|
|
<dd class="tabular-nums">{{if .Lookups.HasTraffic}}{{.Lookups.HitRatio}}%{{else}}—{{end}}</dd>
|
|
</div>
|
|
</dl>
|
|
</div>
|
|
{{end}}
|