This commit is contained in:
npc0-hue
2026-08-26 09:56:43 +08:00
parent 2b4974b561
commit 8e02a316fa
93 changed files with 21749 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
package runtime
import (
"context"
"errors"
"fmt"
"log"
"time"
"browser.local/run/config"
"browser.local/run/protocol"
)
const metricReportTimeout = 5 * time.Second
// MetricCollector provides only generic local utilization data. Game-specific
// observations belong to a plugin-declared bridge, never the Run runtime.
type MetricCollector interface {
Collect(context.Context, string) (MetricUtilization, error)
}
type MetricUtilization struct {
CPUPercent *float64
MemoryPercent *float64
DiskPercent *float64
}
type defaultMetricCollector struct{}
func (defaultMetricCollector) Collect(ctx context.Context, workspaceRoot string) (MetricUtilization, error) {
var utilization MetricUtilization
var collectionErrors []error
if cpuPercent, err := hostCPUPercent(ctx); err != nil {
collectionErrors = append(collectionErrors, fmt.Errorf("collect CPU utilization: %w", err))
} else {
utilization.CPUPercent = &cpuPercent
}
if memoryPercent, err := hostMemoryPercent(); err != nil {
collectionErrors = append(collectionErrors, fmt.Errorf("collect memory utilization: %w", err))
} else {
utilization.MemoryPercent = &memoryPercent
}
if err := ctx.Err(); err != nil {
collectionErrors = append(collectionErrors, err)
} else if diskPercent, err := workspaceDiskPercent(workspaceRoot); err != nil {
collectionErrors = append(collectionErrors, fmt.Errorf("collect disk utilization: %w", err))
} else {
utilization.DiskPercent = &diskPercent
}
return utilization, errors.Join(collectionErrors...)
}
// WithMetricCollector overrides the generic host collector for tests and
// platform-specific collectors. It has no access to plugin inputs or secrets.
func WithMetricCollector(collector MetricCollector) LifecycleExecutorOption {
return func(executor *LifecycleExecutor) {
executor.metricCollector = collector
}
}
func (worker *Worker) reportMetricsDegraded(ctx context.Context, trigger string) {
if err := worker.ReportMetricsOnce(ctx); err != nil {
log.Printf("RUN phase=metrics status=degraded trigger=%s error=%s", safeOptional(trigger), RedactText(err.Error()))
}
}
func (worker *Worker) ReportMetricsOnce(ctx context.Context) error {
if worker.cfg.ComponentKind != config.PackageComponentRun || worker.cfg.ServerInstanceID == "" {
return nil
}
state, err := worker.registeredState()
if err != nil {
return err
}
sample := protocol.MetricSample{ServerInstanceID: worker.cfg.ServerInstanceID, Online: worker.managedServerProcessOnline(state), Source: "run", CollectedAt: time.Now().UTC()}
if worker.metricCollector != nil {
utilization, collectErr := worker.metricCollector.Collect(ctx, worker.cfg.WorkspaceRoot)
sample.CPUPercent = utilization.CPUPercent
sample.MemoryPercent = utilization.MemoryPercent
sample.DiskPercent = utilization.DiskPercent
if collectErr != nil {
log.Printf("RUN phase=metrics status=utilization_unavailable error=%s", RedactText(collectErr.Error()))
}
}
reportCtx, cancel := context.WithTimeout(ctx, metricReportTimeout)
defer cancel()
response, err := worker.client.IngestMetricBatch(reportCtx, protocol.MetricBatchIngestRequest{RunEndpointID: state.RunEndpointID, SessionToken: state.SessionToken, Samples: []protocol.MetricSample{sample}})
if err != nil {
return err
}
if !response.Accepted || response.AcceptedCount != 1 {
return fmt.Errorf("metric batch was not accepted")
}
log.Printf("RUN phase=metrics status=accepted server=%s online=%t", sample.ServerInstanceID, sample.Online)
return nil
}
func (worker *Worker) managedServerProcessOnline(state WorkerState) bool {
source, ok := worker.executor.managed.(ManagedProcessObservationSource)
if !ok {
return false
}
for _, identity := range source.ManagedProcessObservations() {
if identity.ServerInstanceID != worker.cfg.ServerInstanceID || identity.RunEndpointID != state.RunEndpointID {
continue
}
if worker.executor.managed.Status(identity).State == "running" {
return true
}
}
return false
}