Post

Open-Source Continuous Profiling Tools Compared: Pyroscope, Parca, Perforator, gProfiler & the Full Ecosystem

Compare open-source continuous profiling tools including Pyroscope, Parca, and eBPF profilers. Evaluate overhead, language support, and flamegraphs.

Open-Source Continuous Profiling Tools Compared: Pyroscope, Parca, Perforator, gProfiler & the Full Ecosystem

Which open-source continuous profiling tool should you self-host in 2026? This article compares dedicated profiling platforms (Grafana Pyroscope, Parca, Yandex Perforator, Intel gProfiler), fleet-wide eBPF agents (OpenTelemetry eBPF Profiler), language-specific profilers (async-profiler, py-spy, pprof, Clinic.js), kernel/eBPF tools (perf, BCC, bpftrace), and visualization utilities (Speedscope, FlameGraph) — covering architecture, language coverage, overhead, storage, and operational complexity.

Metrics tell you WHEN something is slow. Traces tell you WHERE in the call chain. Profiles tell you WHY — which function, which allocation, which lock. (On commercial APM suites, adding continuous profiling carries steep host surcharges; see our paid observability pricing review).

TL;DR — Quick Recommendations

Use caseBest fitRunner-up
Full profiling platform, Grafana ecosystemPyroscopeParca
eBPF-first, zero-code, Apache 2.0ParcaOTel eBPF Profiler + Pyroscope
Large Linux C/C++/Go/Rust fleetsPerforatorParca
Multi-runtime detection (Java + Python + native)gProfilerPyroscope
Standards-oriented OTel collectionOTel eBPF ProfilerParca Agent
Java/Kotlin production profilingasync-profiler → PyroscopeJFR → Pyroscope
Python production profilingpy-spy → PyroscopegProfiler
Go production profilingpprof → PyroscopeParca
AGPL license unacceptableParca / PerforatorgProfiler

Jump to Section 1 for platform comparison or When to Use What for the full decision table.

This article focuses exclusively on open-source, self-hostable profiling tools — no mandatory commercial licenses, no mandatory SaaS accounts. The profiling ecosystem spans continuous profiling platforms, fleet-wide agents, language-specific profilers, kernel/eBPF tools, and visualization utilities.

Excluded from the primary benchmark: Multi-signal observability platforms (SigNoz, Coroot, SkyWalking, Elastic, OneUptime) — these support profiling but are broader APM/observability systems. For full-platform comparisons, see our companion guide Open-Source Observability Platforms Compared. For empirical CPU/RAM collector and agent overhead benchmarks under stress, see Benchmarking Open-Source Observability.


Table of Contents


Scope & Selection Criteria

CriterionRequirement
Open-sourceOSI-approved license or well-known open license
Self-hostableRuns entirely on your infrastructure
No mandatory commercial licenseFree edition covers primary profiling functionality
No mandatory SaaS accountNo phone-home, no cloud signup required
Primarily designed for profilingNot a multi-signal platform that also does profiling

Legend

SymbolMeaning
✅Supported / available
◐Partial support or requires additional setup
⭐Particular strength or best-in-class
🧪Experimental / early support
—Not supported or not applicable

Taxonomy

The profiling ecosystem is broader than “Pyroscope alternatives.” Only the first category is a direct Pyroscope comparison.

graph TB
    subgraph "1. Continuous Profiling Platforms"
        direction LR
        PYRO[Pyroscope<br/>Always-on, fleet-wide, historical]
        PARCA[Parca<br/>Always-on, eBPF-first]
        PERF[Perforator<br/>Large Linux fleets]
        GP[gProfiler + Studio<br/>Multi-runtime agent]
    end

    subgraph "2. Fleet-Wide Collection Agents"
        direction LR
        OTEL_EBPF[OTel eBPF Profiler<br/>Standards-oriented collector]
        PARCA_AGENT[Parca Agent<br/>eBPF node agent]
        GP_AGENT[gProfiler<br/>Multi-profiler agent]
    end

    subgraph "3. Language-Specific Profilers"
        direction LR
        ASYNC[async-profiler<br/>JVM]
        PYSPY[py-spy<br/>Python]
        PPROF[pprof<br/>Go]
        CLINIC[Clinic.js<br/>Node.js]
    end

    subgraph "4. Kernel/eBPF Profilers"
        direction LR
        PERF_TOOL[perf<br/>Linux kernel]
        BCC[BCC/bpftrace<br/>Dynamic eBPF tracing]
    end

    subgraph "5. Visualization"
        direction LR
        SPEED[Speedscope]
        FG[FlameGraph]
        HOTSPOT[Hotspot]
    end
CategoryWhat it isDirect Pyroscope comparison?
Continuous profiling platformsAlways-on, stores profiles over time, fleet-wide, query historical dataYes
Fleet-wide collection agentsCollect profiles from nodes; require a backend for storage/queryPartial (agent only)
Language-specific profilersDeep runtime insight for one language; often feed into platformsNo
Kernel/eBPF profilersSystem-level tools; on-demand or scriptedNo
VisualizationRender/analyze profiles; no collection or storageNo

Section 1: Open-Source Continuous Profiling Platforms

These are the closest equivalents to Grafana Pyroscope — always-on, fleet-wide, historical continuous profiling with storage, query, and visualization.

The Candidates

PlatformLicenseCollectionBackend/StorageUILanguage CoveragePositioning
Grafana PyroscopeAGPLv3 (server); mostly Apache 2.0 (agents)SDKs, pprof, JFR, eBPF, OTLP profilesBuilt-in, scalable architectureGrafanaBroadIndustry standard, Grafana-native (⭐ 10k · 👥 200+ · Since 2020)
ParcaApache 2.0Parca Agent, eBPF, pprofBuilt-in columnar storageBuilt-in / GrafanaNative + several runtimeseBPF-first, Apache licensed (⭐ 4k · 👥 60+ · Since 2021)
Yandex PerforatorApache 2.0; some GPLv2 componentseBPF agentScalable profile and binary storageBuilt-inC/C++, Go, Rust; experimental Java/PythonLarge native-code fleets (⭐ 800+ · Since 2024)
Intel gProfilerApache 2.0Multiple runtime profilers + perf/eBPFLocal files or Performance StudioFlamegraphs / StudioBroadMulti-runtime detection agent (⭐ 750+ · 👥 20+ · Since 2021)
gProfiler Performance StudioOpen-sourceReceives gProfiler dataCentral aggregationYesDepends on gProfilerCentral aggregation for gProfiler (⭐ 50+ · Since 2022)
OpenTelemetry eBPF ProfilerApache 2.0; eBPF GPLv2System-wide eBPFRequires a backend (Pyroscope/Parca)NoBroad Linux runtime coverageStandards-oriented collector agent (⭐ 800+ · Since 2023 · CNCF)
KubeFlameApache 2.0Kubernetes perf collectionTemporary / localFlamegraphsNative Linux workloadsHistorical / niche (⭐ 200+ · Since 2021)
ProdfilerUnclear OSS statusWhole-system agentHostedYesBroadDo not prioritize

Primary shortlist: Grafana Pyroscope, Parca, Yandex Perforator, Intel gProfiler + Performance Studio, OpenTelemetry eBPF Profiler (as collection agent).

Parca acquisition note: Parca remains open source and maintained following Polar Signals’ August 2026 acquisition by Dash0. Polar Signals announcement.

High-Level Feature Comparison

CapabilityPyroscopeParcaPerforatorgProfilerOTel eBPF Profiler
Complete backendYesYesYesWith Performance StudioNo
Built-in UIThrough GrafanaYesYesLocal flamegraph / StudioNo
Kubernetes deploymentYesYesYesYesYes
eBPF collectionYesYesYesPartiallyYes
Application SDKsYesLimited; accepts standard formatsLimitedNo SDK requiredNo
pprof ingestionYesYesLimitedGenerates collapsed profilesGenerates OTLP profiles
OTLP ProfilesEmerging / experimentalStandards-orientedCheck versionNo primary OTLP pathNative goal
CPU profilingYesYesYesYesYes
Memory/allocation profilesYesYesLimitedSome runtimesDeveloping
Wall-clock profilesRuntime-dependentAgent/profile dependentPrimarily CPUJava/runtime dependentPrimarily CPU
Mutex/block/goroutine profilesGo/Pyroscope integrationsThrough pprofLimitedLimitedNo
Kernel stack visibilityeBPF collectorYesYesYesYes
Non-Linux supportSome language agentsServer yes; eBPF agent LinuxPrimarily LinuxPrimarily LinuxLinux
Object-storage architectureYes (scalable mode)Deployment-dependentScalable profile storageStudio-dependentBackend-dependent
Diff/flamegraph comparisonYesYesYesLimited / StudioBackend-dependent

Architecture Classification

graph TB
    subgraph "Pyroscope"
        direction LR
        P_MONO[Monolithic<br/>Single binary + local storage]
        P_DIST[Microservices<br/>Distributor/Ingester/Compactor/Store-Gateway<br/>+ Object storage]
    end

    subgraph "Parca"
        direction LR
        PA_SERVER[Parca Server<br/>Built-in columnar storage]
        PA_AGENT[Parca Agent<br/>eBPF per node]
    end

    subgraph "Perforator"
        direction LR
        PF_AGENT[eBPF Agent<br/>Per node]
        PF_BACKEND[Profile Storage<br/>+ Binary Storage<br/>+ UI]
    end

    subgraph "gProfiler"
        direction LR
        GP_AGENT2[gProfiler Agent<br/>Multi-runtime per node]
        GP_STUDIO[Performance Studio<br/>Central aggregation]
    end
ArchitectureTrade-off
Pyroscope monolithicSimple; single binary; good for small-medium
Pyroscope microservicesScales horizontally; object storage; more components
ParcaSingle server binary + per-node eBPF agent; columnar storage
PerforatorScalable backend with binary storage (for symbol resolution); eBPF agent
gProfilerAgent-only or agent + Performance Studio; simpler backend story

Collection Mechanisms

MechanismHow it worksOverheadSymbol qualityCode changes required
eBPF (kernel perf_events)Kernel-level stack sampling via BPF programsVery low (<1%)Depends on debug symbols / frame pointersNo
SDK / library instrumentationLanguage runtime hooks (pprof endpoint, JFR)Low (1–3%)⭐ (runtime-aware)Yes (import SDK)
Process sampling (external)External process reads target stack periodicallyLow (1–5%)Good (DWARF / frame pointers)No
Multi-profiler agentDetects runtimes, attaches appropriate profilerLow (1–3%)⭐ (runtime-specific)No
PlatformPrimary mechanismSecondary mechanism
PyroscopeSDK (Go/Java/Python/.NET/Ruby/Node)eBPF (via Alloy/OTel eBPF Profiler)
ParcaeBPF (Parca Agent)pprof pull
PerforatoreBPF agent—
gProfilerMulti-profiler agent (async-profiler, perf, py-spy, etc.)—
OTel eBPF ProfilereBPF (system-wide)—

Language & Runtime Coverage

PlatformJava/JVMGoPythonRustNode.js.NETC/C++Ruby
Pyroscope⭐ (JFR/async-profiler)⭐ (native pprof)✅ (py-spy)✅ (eBPF)✅✅✅ (eBPF)✅
Parca✅ (eBPF + JVMTI)✅ (eBPF + pprof)✅ (eBPF)✅ (eBPF)✅ (eBPF)✅ (eBPF)✅ (eBPF)✅ (eBPF)
Perforator🧪 (experimental)⭐🧪 (experimental)⭐◐◐⭐◐
gProfiler⭐ (async-profiler)✅ (perf/eBPF)⭐ (py-spy)✅ (perf)✅◐✅ (perf)✅
OTel eBPF Profiler✅✅✅✅✅✅✅✅

Key insight: eBPF-based profilers (Parca, Perforator, OTel eBPF) are language-agnostic at the kernel level but depend on debug symbols / frame pointers for quality. SDK-based profilers (Pyroscope agents, async-profiler) give richer runtime-specific data (allocations, goroutines, locks) but require per-language integration.

Storage & Retention

PlatformStorage backendObject storageCompressionTypical retentionEstimated storage (per node/day)
PyroscopeCustom + object storage (S3/GCS/MinIO)⭐ (scalable mode)ZSTD, deduplication14–90 days10–100 MB
ParcaBuilt-in columnar (Parquet-oriented)Deployment-dependentColumnar compression14–30 days10–50 MB
PerforatorScalable profile storage + binary store✅CustomConfigurable10–50 MB
gProfiler + StudioPerformance Studio aggregationStudio-dependent—Configurable5–50 MB
OTel eBPF ProfilerN/A (requires backend)Backend-dependent———

Query & Visualization

PlatformFlamegraphIcicle graphDiff view (before/after)Time-series selectionTag/label filteringTrace → profile linking
Pyroscope⭐⭐⭐⭐⭐✅ (span → flamegraph)
Parca⭐⭐⭐✅✅◐
Perforator⭐✅✅✅✅◐
gProfiler + Studio✅✅◐✅◐—
OTel eBPF Profiler— (backend-dependent)—————

Operational Complexity

PlatformMin RAM (server)Components to runExternal dependenciesUpgrade pathTeam size needed
Pyroscope (monolithic)2 GB1 binary + agentsNone (or object storage for scale)Simple1
Pyroscope (microservices)4 GB+5+ + agentsObject storageSchema versioned1–2
Parca2 GB1 server + per-node agentNoneSimple1
Perforator4 GB+Backend + per-node agentBinary storage componentDeployment-dependent1–2
gProfiler + Studio2 GBAgent + Studio serverStudio dependenciesSimple1
OTel eBPF Profiler—Per-node agent onlyRequires Pyroscope/Parca backendSimple0 (agent)

When to Use What

If you need…Best fitRunner-up
Full profiling platform, multi-language, Grafana ecosystemPyroscopeParca
Apache 2.0 license, zero-instrumentation eBPFParcaPerforator
Large Linux fleets, C/C++/Go/Rust native workloadsPerforatorParca
Multi-runtime agent (Java + Python + native on same hosts)gProfilerPyroscope (with eBPF)
Standards-oriented collection (OTel Profiles)OTel eBPF Profiler → Pyroscope/ParcaParca
Trace-correlated profiling (span → flamegraph)Pyroscope—
Cost attribution (which function costs $$)PyroscopeParca
Simplest deployment (single server + agents)ParcaPyroscope (monolithic)
AGPL license unacceptableParca / PerforatorgProfiler
Existing Grafana investmentPyroscopeParca (Grafana plugin)

Known Limitations

PlatformKey limitation
PyroscopeAGPLv3 server license may be problematic; microservices mode adds complexity; eBPF collection delegates to external agent
ParcaAllocation/heap profiling limited (eBPF sees CPU, not runtime allocations); younger project; UI less polished than Grafana
PerforatorPrimarily Linux/native focused; JVM/Python support experimental; newer project with smaller community
gProfilerBackend story (Performance Studio) less mature than Pyroscope/Parca; no OTLP path; less historical query power
OTel eBPF ProfilerAgent only — requires separate backend; OTel Profiles spec still stabilizing; memory/allocation profiles developing

Section 2: Java and JVM Profilers

These are not centralized continuous profiling platforms, but they collect high-quality JVM profiles and often integrate with Pyroscope.

The Candidates

ToolLicenseProfile typesContinuous production use
async-profilerApache 2.0CPU, allocation, wall clock, locks, native stacksExcellent (⭐ 9.5k · Since 2017)
Java Flight Recorder (JFR)Included in OpenJDKCPU, allocation, GC, locks, I/O, runtime eventsExcellent (Since JDK 11)
JDK Mission ControlOpen-sourceJFR analysis and visualizationAnalysis tool (⭐ 400+ · Since 2018)
Pyroscope Java AgentApache 2.0CPU, wall, allocation, JFR-based profilesExcellent (with Pyroscope) (⭐ 500+ · Since 2021)
Honest ProfilerGPLv2Low-overhead CPU profilingSuitable (⭐ 1.2k · Since 2014)
VisualVMGPLv2 + Classpath exceptionCPU, memory, threads, heapUsually on-demand (⭐ 3.7k · Since 2016)
Eclipse Memory Analyzer (MAT)EPLHeap-dump analysisOffline (Since 2006)
JOLGPLv2Java object layoutSpecialized
JITWatchApache 2.0JIT compilation analysisSpecialized (⭐ 3k · Since 2013)

Commercial (not open-source): JProfiler, YourKit — excluded from this comparison.

1
2
3
4
5
async-profiler or JFR
        ↓
Pyroscope Java Agent
        ↓
Grafana Pyroscope

Parca’s eBPF agent can profile JVM processes, but runtime-aware Java profiling through async-profiler/JFR generally produces more reliable Java method names, allocation information, and lock data.


Section 3: Python Profilers

The Candidates

ToolLicenseCPUMemoryNative extensionsMode
py-spyMITYesNoOptional native modeSampling, attach without code changes (⭐ 13k · Since 2018)
ScaleneApache 2.0YesYesSeparates Python/native timeApplication profiler (⭐ 12k · Since 2019)
AustinGPLv3YesLimitedYesSampling (⭐ 1.5k · Since 2018)
PyinstrumentBSDYesNoLimitedStatistical profiler (⭐ 6.5k · Since 2014)
MemrayApache 2.0NoExcellentYesAllocation/memory profiler (⭐ 13.5k · Since 2022)
FilApache 2.0NoYesSome native trackingPeak-memory profiler (⭐ 800+ · Since 2020)
YappiMITYesNoThreads and asyncioDeterministic/statistical (⭐ 1.4k · Since 2011)
cProfilePython standard libraryYesNoLimitedDeterministic (built-in)
line_profilerBSDLine-levelNoNoInstrumented (⭐ 2.5k · Since 2008)
memory_profilerBSDNoLine-level memoryNoOn-demand (⭐ 4.3k · Since 2011)
Pyroscope PythonOpen-sourceYesDepending on integrationRuntime-dependentContinuous (part of Pyroscope)
  • CPU profiling (production): py-spy → Pyroscope
  • Memory/allocation investigations: Memray
  • Fleet-wide continuous profiles: Pyroscope Python SDK or gProfiler agent
  • Multi-runtime hosts (Java + Python + native): gProfiler

Section 4: Go Profilers

ToolLicensePurpose
pprofApache 2.0CPU, heap, allocation, mutex, block, goroutine profiles (⭐ 8k · Since 2016)
Go net/http/pprofBSD-style Go licenseExposes application profiles over HTTP (built-in since Go 1.0)
Pyroscope Go clientApache 2.0Continuously sends Go profiles to Pyroscope (⭐ 200+ · Since 2021)
fgprofMITCombined on-CPU and off-CPU profiling (⭐ 3k · Since 2020)
go-torchMITFlamegraphs from Go profiles; archived (⭐ 4.6k · Since 2015 · Archived)
go tool traceGo licenseScheduler, goroutine, GC, runtime tracing (built-in)

Go has the best native integration with Pyroscope and Parca — both understand the pprof format natively.


Section 5: Node.js and Browser/UI Profilers

Node.js

ToolLicensePurpose
Clinic.jsApache 2.0CPU, event-loop, async, I/O diagnosis
0xMITV8 flamegraphs
Node.js --profNode.js licenseBuilt-in V8 CPU profiler
Node.js InspectorNode.js licenseCPU and heap profiling
Pyroscope Node.js agentOpen-sourceContinuous CPU and wall profiles
pprof for Node.jsApache 2.0pprof-compatible profiles
heapdumpMITV8 heap snapshots

Browser/Frontend

ToolAvailabilityPurpose
Firefox ProfilerOpen-sourceBrowser CPU, rendering, JavaScript, networking profiles
Chromium DevToolsOpen-source (Chromium component)Performance timeline, CPU and heap profiling
PerfettoApache 2.0Browser, Android, system trace analysis
SpeedscopeMITInteractive flamegraph viewer
LighthouseApache 2.0Web-performance auditing (not continuous profiling)
WebPageTestPolyform Shield / source-availableBrowser performance testing

Continuous production profiling of browser code is uncommon due to privacy, overhead, and browser security restrictions. Browser profiling uses sampled RUM, traces, and lab tools rather than an always-on Pyroscope-style agent.


Section 6: Linux, Native and eBPF Profiling Tools

ToolLicensePrimary capability
perfGPLv2CPU, hardware counters, call stacks, kernel profiling (Linux kernel, Since 2009)
BCCApache 2.0Collection of eBPF performance tools (⭐ 21k · Since 2015)
bpftraceApache 2.0High-level dynamic eBPF tracing (⭐ 8.9k · Since 2018)
ValgrindGPLv2Memory errors, heap, CPU simulation (Since 2000)
CallgrindGPLv2Call-graph profiling (part of Valgrind)
gperftoolsBSDCPU and heap profiling (⭐ 8.5k · Since 2005)
HeaptrackLGPLNative heap-allocation profiling (⭐ 1.5k · Since 2014)
HotspotGPLv2GUI for Linux perf data (⭐ 4.2k · Since 2016)
FlameGraphCDDLFlamegraph-generation scripts (⭐ 17.5k · Since 2011)
uftraceGPLv2Function call tracing and profiling (⭐ 3.2k · Since 2014)
TracyBSDReal-time frame and application profiler (⭐ 10k · Since 2017)
OrbitBSD-2-ClauseNative application profiler (⭐ 4.1k · Since 2019)
cozBSDCausal profiling (⭐ 4.1k · Since 2015)
OProfileGPLSystem-wide statistical profiling (Since 2002)
SysprofGPLLinux system profiler (Since 2004)
SamplyMIT / Apache 2.0Sampling profiler with Firefox Profiler UI (⭐ 2.5k · Since 2022)

Section 7: Profile Formats and Visualization

Tool / SpecificationPurpose
pprofWidely supported profile format and visualization tool
OpenTelemetry ProfilesEmerging vendor-neutral profile signal (stabilizing)
JFRJVM recording format
SpeedscopeInteractive profile viewer and JSON format
FlameGraphFolded-stack flamegraph generation
InfernoRust flamegraph-generation library
d3-flame-graphD3.js flamegraph visualization

Continuous profiling platforms (primary benchmark)

  • Grafana Pyroscope
  • Parca
  • Yandex Perforator
  • Intel gProfiler + Performance Studio

Benchmark dimensions: Collection overhead, symbol resolution quality, storage efficiency, query latency (time-range flamegraph), diff comparison, Kubernetes resource usage, retention cost, multi-language coverage accuracy, horizontal scaling, failure recovery.

Collection agents (separate evaluation)

  • Pyroscope language agents (Java, Go, Python, Node, .NET, Ruby)
  • Parca Agent
  • OpenTelemetry eBPF Profiler
  • gProfiler agent

Runtime-specific profilers (per-language evaluation)

  • Java/Kotlin: async-profiler, JFR, JDK Mission Control
  • Python: py-spy, Scalene, Memray
  • Go: pprof, fgprof
  • Node.js: Clinic.js, 0x
  • Native/Linux: perf, BCC, bpftrace, Valgrind

Visualization tools

  • Speedscope
  • FlameGraph scripts
  • Hotspot (perf data GUI)
  • Grafana (Pyroscope datasource)

FAQ

What is continuous profiling and why do I need it? Continuous profiling samples your application’s CPU, memory, and lock usage in production at all times (with <1–3% overhead). Unlike on-demand profiling, you can look back at any time window to understand why latency spiked — without needing to reproduce the issue.

What is the best open-source alternative to Datadog Continuous Profiler? Grafana Pyroscope — it covers the same languages (Java, Go, Python, .NET, Ruby, Node.js), supports flame graphs, diff views, time-range selection, and integrates with Grafana for trace-to-profile linking. Parca is the runner-up with a simpler eBPF-first approach.

Should I use Pyroscope or Parca? Use Pyroscope if you want the broadest language coverage via runtime-aware SDKs (async-profiler for Java, py-spy for Python), Grafana integration, and a mature scalable backend. Use Parca if you want zero-code eBPF profiling with an Apache 2.0 license and simpler deployment (single binary + agent).

What overhead does continuous profiling add? eBPF-based profilers (Parca, OTel eBPF Profiler) typically add <1% CPU overhead. SDK-based profilers (Pyroscope agents using async-profiler or py-spy) add 1–3%. Both are safe for production.

Can I link traces to profiles (span-level profiling)? Yes — Pyroscope supports linking a specific trace span to the corresponding flame graph, showing exactly which functions consumed time during that request. This requires the Pyroscope SDK or OTel integration with span profiling enabled.

What is the OpenTelemetry Profiling signal? OpenTelemetry is standardizing a Profiles signal (alongside Metrics, Logs, Traces). The OTel eBPF Profiler generates profiles in this format. As of 2026, the specification is stabilizing — Pyroscope and Parca are adding OTLP Profiles ingestion support.

🧭 The Complete Observability Guide & Comparison Series


References

Continuous Profiling Platforms

Java/JVM

Python

Go

Node.js

Linux/eBPF

Visualization & Formats


Last verified: September 2026. Features, licensing, and performance characteristics change — always check official sources.

This post is licensed under CC BY 4.0 by the author.