113 lines
3.9 KiB
Go
113 lines
3.9 KiB
Go
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
|
|
}
|