Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Implemented the PhpAnalyzerPlugin to analyze PHP projects.
- Created ComposerLockData class to represent data from composer.lock files.
- Developed ComposerLockReader to load and parse composer.lock files asynchronously.
- Introduced ComposerPackage class to encapsulate package details.
- Added PhpPackage class to represent PHP packages with metadata and evidence.
- Implemented PhpPackageCollector to gather packages from ComposerLockData.
- Created PhpLanguageAnalyzer to perform analysis and emit results.
- Added capability signals for known PHP frameworks and CMS.
- Developed unit tests for the PHP language analyzer and its components.
- Included sample composer.lock and expected output for testing.
- Updated project files for the new PHP analyzer library and tests.
This commit is contained in:
StellaOps Bot
2025-11-22 14:02:49 +02:00
parent a7f3c7869a
commit b6b9ffc050
158 changed files with 16272 additions and 809 deletions

View File

@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using System.Diagnostics.Metrics;
namespace StellaOps.Findings.Ledger.Observability;
@@ -6,10 +7,16 @@ internal static class LedgerMetrics
{
private static readonly Meter Meter = new("StellaOps.Findings.Ledger");
private static readonly Histogram<double> WriteDurationSeconds = Meter.CreateHistogram<double>(
"ledger_write_duration_seconds",
unit: "s",
description: "Latency of successful ledger append operations.");
// Compatibility with earlier drafts
private static readonly Histogram<double> WriteLatencySeconds = Meter.CreateHistogram<double>(
"ledger_write_latency_seconds",
unit: "s",
description: "Latency of successful ledger append operations.");
description: "Deprecated alias for ledger_write_duration_seconds.");
private static readonly Counter<long> EventsTotal = Meter.CreateCounter<long>(
"ledger_events_total",
@@ -20,15 +27,40 @@ internal static class LedgerMetrics
unit: "s",
description: "Duration to apply a ledger event to the finding projection.");
private static readonly Histogram<double> ProjectionLagSeconds = Meter.CreateHistogram<double>(
"ledger_projection_lag_seconds",
private static readonly Histogram<double> ProjectionRebuildSeconds = Meter.CreateHistogram<double>(
"ledger_projection_rebuild_seconds",
unit: "s",
description: "Lag between ledger recorded_at and projection application time.");
description: "Duration of projection replay/rebuild batches.");
private static readonly Counter<long> ProjectionEventsTotal = Meter.CreateCounter<long>(
"ledger_projection_events_total",
description: "Number of ledger events applied to projections.");
private static readonly Histogram<double> MerkleAnchorDurationSeconds = Meter.CreateHistogram<double>(
"ledger_merkle_anchor_duration_seconds",
unit: "s",
description: "Duration to persist Merkle anchor batches.");
private static readonly Counter<long> MerkleAnchorFailures = Meter.CreateCounter<long>(
"ledger_merkle_anchor_failures_total",
description: "Count of Merkle anchor failures by reason.");
private static readonly ObservableGauge<double> ProjectionLagGauge =
Meter.CreateObservableGauge("ledger_projection_lag_seconds", ObserveProjectionLag, unit: "s",
description: "Lag between ledger recorded_at and projection application time.");
private static readonly ObservableGauge<long> IngestBacklogGauge =
Meter.CreateObservableGauge("ledger_ingest_backlog_events", ObserveBacklog,
description: "Number of events buffered for ingestion/anchoring.");
private static readonly ObservableGauge<long> DbConnectionsGauge =
Meter.CreateObservableGauge("ledger_db_connections_active", ObserveDbConnections,
description: "Active PostgreSQL connections by role.");
private static readonly ConcurrentDictionary<string, double> ProjectionLagByTenant = new(StringComparer.Ordinal);
private static readonly ConcurrentDictionary<string, long> DbConnectionsByRole = new(StringComparer.OrdinalIgnoreCase);
private static long _ingestBacklog;
public static void RecordWriteSuccess(TimeSpan duration, string? tenantId, string? eventType, string? source)
{
var tags = new KeyValuePair<string, object?>[]
@@ -38,6 +70,7 @@ internal static class LedgerMetrics
new("source", source ?? string.Empty)
};
WriteDurationSeconds.Record(duration.TotalSeconds, tags);
WriteLatencySeconds.Record(duration.TotalSeconds, tags);
EventsTotal.Add(1, tags);
}
@@ -59,7 +92,90 @@ internal static class LedgerMetrics
};
ProjectionApplySeconds.Record(duration.TotalSeconds, tags);
ProjectionLagSeconds.Record(lagSeconds, tags);
ProjectionEventsTotal.Add(1, tags);
UpdateProjectionLag(tenantId, lagSeconds);
}
public static void RecordProjectionRebuild(TimeSpan duration, string? tenantId, string scenario)
{
var tags = new KeyValuePair<string, object?>[]
{
new("tenant", tenantId ?? string.Empty),
new("scenario", scenario)
};
ProjectionRebuildSeconds.Record(duration.TotalSeconds, tags);
}
public static void RecordMerkleAnchorDuration(TimeSpan duration, string tenantId, int leafCount)
{
var tags = new KeyValuePair<string, object?>[]
{
new("tenant", tenantId),
new("leaf_count", leafCount)
};
MerkleAnchorDurationSeconds.Record(duration.TotalSeconds, tags);
}
public static void RecordMerkleAnchorFailure(string tenantId, string reason)
{
var tags = new KeyValuePair<string, object?>[]
{
new("tenant", tenantId),
new("reason", reason)
};
MerkleAnchorFailures.Add(1, tags);
}
public static void IncrementBacklog() => Interlocked.Increment(ref _ingestBacklog);
public static void DecrementBacklog()
{
var value = Interlocked.Decrement(ref _ingestBacklog);
if (value < 0)
{
Interlocked.Exchange(ref _ingestBacklog, 0);
}
}
public static void ConnectionOpened(string role)
{
var normalized = NormalizeRole(role);
DbConnectionsByRole.AddOrUpdate(normalized, _ => 1, (_, current) => current + 1);
}
public static void ConnectionClosed(string role)
{
var normalized = NormalizeRole(role);
DbConnectionsByRole.AddOrUpdate(normalized, _ => 0, (_, current) => Math.Max(0, current - 1));
}
public static void UpdateProjectionLag(string? tenantId, double lagSeconds)
{
var key = string.IsNullOrWhiteSpace(tenantId) ? string.Empty : tenantId;
ProjectionLagByTenant[key] = lagSeconds < 0 ? 0 : lagSeconds;
}
private static IEnumerable<Measurement<double>> ObserveProjectionLag()
{
foreach (var kvp in ProjectionLagByTenant)
{
yield return new Measurement<double>(kvp.Value, new KeyValuePair<string, object?>("tenant", kvp.Key));
}
}
private static IEnumerable<Measurement<long>> ObserveBacklog()
{
yield return new Measurement<long>(Interlocked.Read(ref _ingestBacklog));
}
private static IEnumerable<Measurement<long>> ObserveDbConnections()
{
foreach (var kvp in DbConnectionsByRole)
{
yield return new Measurement<long>(kvp.Value, new KeyValuePair<string, object?>("role", kvp.Key));
}
}
private static string NormalizeRole(string role) => string.IsNullOrWhiteSpace(role) ? "unspecified" : role.ToLowerInvariant();
}