Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
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:
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user