1package main 2 3import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "sync" 9 "time" 10 11 "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" 12 runhcsopts "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" 13 "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats" 14 "github.com/Microsoft/hcsshim/internal/cmd" 15 "github.com/Microsoft/hcsshim/internal/cow" 16 "github.com/Microsoft/hcsshim/internal/hcs" 17 "github.com/Microsoft/hcsshim/internal/hcsoci" 18 "github.com/Microsoft/hcsshim/internal/log" 19 "github.com/Microsoft/hcsshim/internal/oci" 20 "github.com/Microsoft/hcsshim/internal/resources" 21 "github.com/Microsoft/hcsshim/internal/schema1" 22 hcsschema "github.com/Microsoft/hcsshim/internal/schema2" 23 "github.com/Microsoft/hcsshim/internal/shimdiag" 24 "github.com/Microsoft/hcsshim/internal/uvm" 25 "github.com/Microsoft/hcsshim/osversion" 26 eventstypes "github.com/containerd/containerd/api/events" 27 "github.com/containerd/containerd/errdefs" 28 "github.com/containerd/containerd/runtime" 29 "github.com/containerd/containerd/runtime/v2/task" 30 "github.com/containerd/typeurl" 31 specs "github.com/opencontainers/runtime-spec/specs-go" 32 "github.com/pkg/errors" 33 "github.com/sirupsen/logrus" 34 "go.opencensus.io/trace" 35) 36 37func newHcsStandaloneTask(ctx context.Context, events publisher, req *task.CreateTaskRequest, s *specs.Spec) (shimTask, error) { 38 log.G(ctx).WithField("tid", req.ID).Debug("newHcsStandaloneTask") 39 40 ct, _, err := oci.GetSandboxTypeAndID(s.Annotations) 41 if err != nil { 42 return nil, err 43 } 44 if ct != oci.KubernetesContainerTypeNone { 45 return nil, errors.Wrapf( 46 errdefs.ErrFailedPrecondition, 47 "cannot create standalone task, expected no annotation: '%s': got '%s'", 48 oci.KubernetesContainerTypeAnnotation, 49 ct) 50 } 51 52 owner := filepath.Base(os.Args[0]) 53 54 var parent *uvm.UtilityVM 55 if osversion.Get().Build >= osversion.RS5 && oci.IsIsolated(s) { 56 // Create the UVM parent 57 opts, err := oci.SpecToUVMCreateOpts(ctx, s, fmt.Sprintf("%s@vm", req.ID), owner) 58 if err != nil { 59 return nil, err 60 } 61 switch opts.(type) { 62 case *uvm.OptionsLCOW: 63 lopts := (opts).(*uvm.OptionsLCOW) 64 parent, err = uvm.CreateLCOW(ctx, lopts) 65 if err != nil { 66 return nil, err 67 } 68 case *uvm.OptionsWCOW: 69 wopts := (opts).(*uvm.OptionsWCOW) 70 71 // In order for the UVM sandbox.vhdx not to collide with the actual 72 // nested Argon sandbox.vhdx we append the \vm folder to the last 73 // entry in the list. 74 layersLen := len(s.Windows.LayerFolders) 75 layers := make([]string, layersLen) 76 copy(layers, s.Windows.LayerFolders) 77 78 vmPath := filepath.Join(layers[layersLen-1], "vm") 79 err := os.MkdirAll(vmPath, 0) 80 if err != nil { 81 return nil, err 82 } 83 layers[layersLen-1] = vmPath 84 wopts.LayerFolders = layers 85 86 parent, err = uvm.CreateWCOW(ctx, wopts) 87 if err != nil { 88 return nil, err 89 } 90 } 91 err = parent.Start(ctx) 92 if err != nil { 93 parent.Close() 94 } 95 } else if !oci.IsWCOW(s) { 96 return nil, errors.Wrap(errdefs.ErrFailedPrecondition, "oci spec does not contain WCOW or LCOW spec") 97 } 98 99 shim, err := newHcsTask(ctx, events, parent, true, req, s) 100 if err != nil { 101 if parent != nil { 102 parent.Close() 103 } 104 return nil, err 105 } 106 return shim, nil 107} 108 109// newHcsTask creates a container within `parent` and its init exec process in 110// the `shimExecCreated` state and returns the task that tracks its lifetime. 111// 112// If `parent == nil` the container is created on the host. 113func newHcsTask( 114 ctx context.Context, 115 events publisher, 116 parent *uvm.UtilityVM, 117 ownsParent bool, 118 req *task.CreateTaskRequest, 119 s *specs.Spec) (_ shimTask, err error) { 120 log.G(ctx).WithFields(logrus.Fields{ 121 "tid": req.ID, 122 "ownsParent": ownsParent, 123 }).Debug("newHcsTask") 124 125 owner := filepath.Base(os.Args[0]) 126 127 io, err := cmd.NewNpipeIO(ctx, req.Stdin, req.Stdout, req.Stderr, req.Terminal) 128 if err != nil { 129 return nil, err 130 } 131 132 var netNS string 133 if s.Windows != nil && 134 s.Windows.Network != nil { 135 netNS = s.Windows.Network.NetworkNamespace 136 } 137 138 var shimOpts *runhcsopts.Options 139 if req.Options != nil { 140 v, err := typeurl.UnmarshalAny(req.Options) 141 if err != nil { 142 return nil, err 143 } 144 shimOpts = v.(*runhcsopts.Options) 145 } 146 147 opts := hcsoci.CreateOptions{ 148 ID: req.ID, 149 Owner: owner, 150 Spec: s, 151 HostingSystem: parent, 152 NetworkNamespace: netNS, 153 } 154 155 if shimOpts != nil { 156 opts.ScaleCPULimitsToSandbox = shimOpts.ScaleCpuLimitsToSandbox 157 } 158 159 system, resources, err := hcsoci.CreateContainer(ctx, &opts) 160 if err != nil { 161 return nil, err 162 } 163 164 ht := &hcsTask{ 165 events: events, 166 id: req.ID, 167 isWCOW: oci.IsWCOW(s), 168 c: system, 169 cr: resources, 170 ownsHost: ownsParent, 171 host: parent, 172 closed: make(chan struct{}), 173 } 174 ht.init = newHcsExec( 175 ctx, 176 events, 177 req.ID, 178 parent, 179 system, 180 req.ID, 181 req.Bundle, 182 ht.isWCOW, 183 s.Process, 184 io) 185 186 if parent != nil { 187 // We have a parent UVM. Listen for its exit and forcibly close this 188 // task. This is not expected but in the event of a UVM crash we need to 189 // handle this case. 190 go ht.waitForHostExit() 191 } 192 // In the normal case the `Signal` call from the caller killed this task's 193 // init process. 194 go ht.waitInitExit() 195 196 // Publish the created event 197 ht.events.publishEvent( 198 ctx, 199 runtime.TaskCreateEventTopic, 200 &eventstypes.TaskCreate{ 201 ContainerID: req.ID, 202 Bundle: req.Bundle, 203 Rootfs: req.Rootfs, 204 IO: &eventstypes.TaskIO{ 205 Stdin: req.Stdin, 206 Stdout: req.Stdout, 207 Stderr: req.Stderr, 208 Terminal: req.Terminal, 209 }, 210 Checkpoint: "", 211 Pid: uint32(ht.init.Pid()), 212 }) 213 return ht, nil 214} 215 216var _ = (shimTask)(&hcsTask{}) 217 218// hcsTask is a generic task that represents a WCOW Container (process or 219// hypervisor isolated), or a LCOW Container. This task MAY own the UVM the 220// container is in but in the case of a POD it may just track the UVM for 221// container lifetime management. In the case of ownership when the init 222// task/exec is stopped the UVM itself will be stopped as well. 223type hcsTask struct { 224 events publisher 225 // id is the id of this task when it is created. 226 // 227 // It MUST be treated as read only in the liftetime of the task. 228 id string 229 // isWCOW is set to `true` if this is a task representing a Windows container. 230 // 231 // It MUST be treated as read only in the liftetime of the task. 232 isWCOW bool 233 // c is the container backing this task. 234 // 235 // It MUST be treated as read only in the lifetime of this task EXCEPT after 236 // a Kill to the init task in which it must be shutdown. 237 c cow.Container 238 // cr is the container resources this task is holding. 239 // 240 // It MUST be treated as read only in the lifetime of this task EXCEPT after 241 // a Kill to the init task in which all resources must be released. 242 cr *resources.Resources 243 // init is the init process of the container. 244 // 245 // Note: the invariant `container state == init.State()` MUST be true. IE: 246 // if the init process exits the container as a whole and all exec's MUST 247 // exit. 248 // 249 // It MUST be treated as read only in the lifetime of the task. 250 init shimExec 251 // ownsHost is `true` if this task owns `host`. If so when this tasks init 252 // exec shuts down it is required that `host` be shut down as well. 253 ownsHost bool 254 // host is the hosting VM for this exec if hypervisor isolated. If 255 // `host==nil` this is an Argon task so no UVM cleanup is required. 256 // 257 // NOTE: if `osversion.Get().Build < osversion.RS5` this will always be 258 // `nil`. 259 host *uvm.UtilityVM 260 261 // ecl is the exec create lock for all non-init execs and MUST be held 262 // durring create to prevent ID duplication. 263 ecl sync.Mutex 264 execs sync.Map 265 266 closed chan struct{} 267 closeOnce sync.Once 268 // closeHostOnce is used to close `host`. This will only be used if 269 // `ownsHost==true` and `host != nil`. 270 closeHostOnce sync.Once 271} 272 273func (ht *hcsTask) ID() string { 274 return ht.id 275} 276 277func (ht *hcsTask) CreateExec(ctx context.Context, req *task.ExecProcessRequest, spec *specs.Process) error { 278 ht.ecl.Lock() 279 defer ht.ecl.Unlock() 280 281 // If the task exists or we got a request for "" which is the init task 282 // fail. 283 if _, loaded := ht.execs.Load(req.ExecID); loaded || req.ExecID == "" { 284 return errors.Wrapf(errdefs.ErrAlreadyExists, "exec: '%s' in task: '%s' already exists", req.ExecID, ht.id) 285 } 286 287 if ht.init.State() != shimExecStateRunning { 288 return errors.Wrapf(errdefs.ErrFailedPrecondition, "exec: '' in task: '%s' must be running to create additional execs", ht.id) 289 } 290 291 io, err := cmd.NewNpipeIO(ctx, req.Stdin, req.Stdout, req.Stderr, req.Terminal) 292 if err != nil { 293 return err 294 } 295 he := newHcsExec(ctx, ht.events, ht.id, ht.host, ht.c, req.ExecID, ht.init.Status().Bundle, ht.isWCOW, spec, io) 296 ht.execs.Store(req.ExecID, he) 297 298 // Publish the created event 299 ht.events.publishEvent( 300 ctx, 301 runtime.TaskExecAddedEventTopic, 302 &eventstypes.TaskExecAdded{ 303 ContainerID: ht.id, 304 ExecID: req.ExecID, 305 }) 306 307 return nil 308} 309 310func (ht *hcsTask) GetExec(eid string) (shimExec, error) { 311 if eid == "" { 312 return ht.init, nil 313 } 314 raw, loaded := ht.execs.Load(eid) 315 if !loaded { 316 return nil, errors.Wrapf(errdefs.ErrNotFound, "exec: '%s' in task: '%s' not found", eid, ht.id) 317 } 318 return raw.(shimExec), nil 319} 320 321func (ht *hcsTask) KillExec(ctx context.Context, eid string, signal uint32, all bool) error { 322 e, err := ht.GetExec(eid) 323 if err != nil { 324 return err 325 } 326 if all && eid != "" { 327 return errors.Wrapf(errdefs.ErrFailedPrecondition, "cannot signal all for non-empty exec: '%s'", eid) 328 } 329 if all { 330 // We are in a kill all on the init task. Signal everything. 331 ht.execs.Range(func(key, value interface{}) bool { 332 err := value.(shimExec).Kill(ctx, signal) 333 if err != nil { 334 log.G(ctx).WithFields(logrus.Fields{ 335 "eid": key, 336 logrus.ErrorKey: err, 337 }).Warn("failed to kill exec in task") 338 } 339 340 // iterate all 341 return false 342 }) 343 } 344 if signal == 0x9 && eid == "" && ht.host != nil { 345 // If this is a SIGKILL against the init process we start a background 346 // timer and wait on either the timer expiring or the process exiting 347 // cleanly. If the timer exires first we forcibly close the UVM as we 348 // assume the guest is misbehaving for some reason. 349 go func() { 350 t := time.NewTimer(30 * time.Second) 351 execExited := make(chan struct{}) 352 go func() { 353 e.Wait() 354 close(execExited) 355 }() 356 select { 357 case <-execExited: 358 t.Stop() 359 case <-t.C: 360 // Safe to call multiple times if called previously on 361 // successful shutdown. 362 ht.host.Close() 363 } 364 }() 365 } 366 return e.Kill(ctx, signal) 367} 368 369func (ht *hcsTask) DeleteExec(ctx context.Context, eid string) (int, uint32, time.Time, error) { 370 e, err := ht.GetExec(eid) 371 if err != nil { 372 return 0, 0, time.Time{}, err 373 } 374 if eid == "" { 375 // We are deleting the init exec. Forcibly exit any additional exec's. 376 ht.execs.Range(func(key, value interface{}) bool { 377 ex := value.(shimExec) 378 if s := ex.State(); s != shimExecStateExited { 379 ex.ForceExit(ctx, 1) 380 } 381 382 // iterate next 383 return false 384 }) 385 } 386 switch state := e.State(); state { 387 case shimExecStateCreated: 388 e.ForceExit(ctx, 0) 389 case shimExecStateRunning: 390 return 0, 0, time.Time{}, newExecInvalidStateError(ht.id, eid, state, "delete") 391 } 392 status := e.Status() 393 if eid != "" { 394 ht.execs.Delete(eid) 395 } 396 397 // Publish the deleted event 398 ht.events.publishEvent( 399 ctx, 400 runtime.TaskDeleteEventTopic, 401 &eventstypes.TaskDelete{ 402 ContainerID: ht.id, 403 ID: eid, 404 Pid: status.Pid, 405 ExitStatus: status.ExitStatus, 406 ExitedAt: status.ExitedAt, 407 }) 408 409 return int(status.Pid), status.ExitStatus, status.ExitedAt, nil 410} 411 412func (ht *hcsTask) Pids(ctx context.Context) ([]options.ProcessDetails, error) { 413 // Map all user created exec's to pid/exec-id 414 pidMap := make(map[int]string) 415 ht.execs.Range(func(key, value interface{}) bool { 416 ex := value.(shimExec) 417 pidMap[ex.Pid()] = ex.ID() 418 419 // Iterate all 420 return false 421 }) 422 pidMap[ht.init.Pid()] = ht.init.ID() 423 424 // Get the guest pids 425 props, err := ht.c.Properties(ctx, schema1.PropertyTypeProcessList) 426 if err != nil { 427 return nil, err 428 } 429 430 // Copy to pid/exec-id pair's 431 pairs := make([]options.ProcessDetails, len(props.ProcessList)) 432 for i, p := range props.ProcessList { 433 pairs[i].ImageName = p.ImageName 434 pairs[i].CreatedAt = p.CreateTimestamp 435 pairs[i].KernelTime_100Ns = p.KernelTime100ns 436 pairs[i].MemoryCommitBytes = p.MemoryCommitBytes 437 pairs[i].MemoryWorkingSetPrivateBytes = p.MemoryWorkingSetPrivateBytes 438 pairs[i].MemoryWorkingSetSharedBytes = p.MemoryWorkingSetSharedBytes 439 pairs[i].ProcessID = p.ProcessId 440 pairs[i].UserTime_100Ns = p.KernelTime100ns 441 442 if eid, ok := pidMap[int(p.ProcessId)]; ok { 443 pairs[i].ExecID = eid 444 } 445 } 446 return pairs, nil 447} 448 449func (ht *hcsTask) Wait() *task.StateResponse { 450 <-ht.closed 451 return ht.init.Wait() 452} 453 454func (ht *hcsTask) waitInitExit() { 455 ctx, span := trace.StartSpan(context.Background(), "hcsTask::waitInitExit") 456 defer span.End() 457 span.AddAttributes(trace.StringAttribute("tid", ht.id)) 458 459 // Wait for it to exit on its own 460 ht.init.Wait() 461 462 // Close the host and event the exit 463 ht.close(ctx) 464} 465 466// waitForHostExit waits for the host virtual machine to exit. Once exited 467// forcibly exits all additional exec's in this task. 468// 469// This MUST be called via a goroutine to wait on a background thread. 470// 471// Note: For Windows process isolated containers there is no host virtual 472// machine so this should not be called. 473func (ht *hcsTask) waitForHostExit() { 474 ctx, span := trace.StartSpan(context.Background(), "hcsTask::waitForHostExit") 475 defer span.End() 476 span.AddAttributes(trace.StringAttribute("tid", ht.id)) 477 478 err := ht.host.Wait() 479 if err != nil { 480 log.G(ctx).WithError(err).Error("failed to wait for host virtual machine exit") 481 } else { 482 log.G(ctx).Debug("host virtual machine exited") 483 } 484 485 ht.execs.Range(func(key, value interface{}) bool { 486 ex := value.(shimExec) 487 ex.ForceExit(ctx, 1) 488 489 // iterate all 490 return false 491 }) 492 ht.init.ForceExit(ctx, 1) 493 ht.closeHost(ctx) 494} 495 496// close shuts down the container that is owned by this task and if 497// `ht.ownsHost` will shutdown the hosting VM the container was placed in. 498// 499// NOTE: For Windows process isolated containers `ht.ownsHost==true && ht.host 500// == nil`. 501func (ht *hcsTask) close(ctx context.Context) { 502 ht.closeOnce.Do(func() { 503 log.G(ctx).Debug("hcsTask::closeOnce") 504 505 // ht.c should never be nil for a real task but in testing we stub 506 // this to avoid a nil dereference. We really should introduce a 507 // method or interface for ht.c operations that we can stub for 508 // testing. 509 if ht.c != nil { 510 // Do our best attempt to tear down the container. 511 var werr error 512 ch := make(chan struct{}) 513 go func() { 514 werr = ht.c.Wait() 515 close(ch) 516 }() 517 err := ht.c.Shutdown(ctx) 518 if err != nil { 519 log.G(ctx).WithError(err).Error("failed to shutdown container") 520 } else { 521 t := time.NewTimer(time.Second * 30) 522 select { 523 case <-ch: 524 err = werr 525 t.Stop() 526 if err != nil { 527 log.G(ctx).WithError(err).Error("failed to wait for container shutdown") 528 } 529 case <-t.C: 530 log.G(ctx).WithError(hcs.ErrTimeout).Error("failed to wait for container shutdown") 531 } 532 } 533 534 if err != nil { 535 err = ht.c.Terminate(ctx) 536 if err != nil { 537 log.G(ctx).WithError(err).Error("failed to terminate container") 538 } else { 539 t := time.NewTimer(time.Second * 30) 540 select { 541 case <-ch: 542 err = werr 543 t.Stop() 544 if err != nil { 545 log.G(ctx).WithError(err).Error("failed to wait for container terminate") 546 } 547 case <-t.C: 548 log.G(ctx).WithError(hcs.ErrTimeout).Error("failed to wait for container terminate") 549 } 550 } 551 } 552 553 // Release any resources associated with the container. 554 if err := resources.ReleaseResources(ctx, ht.cr, ht.host, true); err != nil { 555 log.G(ctx).WithError(err).Error("failed to release container resources") 556 } 557 558 // Close the container handle invalidating all future access. 559 if err := ht.c.Close(); err != nil { 560 log.G(ctx).WithError(err).Error("failed to close container") 561 } 562 } 563 ht.closeHost(ctx) 564 }) 565} 566 567// closeHost safely closes the hosting UVM if this task is the owner. Once 568// closed and all resources released it events the `runtime.TaskExitEventTopic` 569// for all upstream listeners. 570// 571// Note: If this is a process isolated task the hosting UVM is simply a `noop`. 572// 573// This call is idempotent and safe to call multiple times. 574func (ht *hcsTask) closeHost(ctx context.Context) { 575 ht.closeHostOnce.Do(func() { 576 log.G(ctx).Debug("hcsTask::closeHostOnce") 577 578 if ht.ownsHost && ht.host != nil { 579 if err := ht.host.Close(); err != nil { 580 log.G(ctx).WithError(err).Error("failed host vm shutdown") 581 } 582 } 583 // Send the `init` exec exit notification always. 584 exit := ht.init.Status() 585 ht.events.publishEvent( 586 ctx, 587 runtime.TaskExitEventTopic, 588 &eventstypes.TaskExit{ 589 ContainerID: ht.id, 590 ID: exit.ID, 591 Pid: uint32(exit.Pid), 592 ExitStatus: exit.ExitStatus, 593 ExitedAt: exit.ExitedAt, 594 }) 595 close(ht.closed) 596 }) 597} 598 599func (ht *hcsTask) ExecInHost(ctx context.Context, req *shimdiag.ExecProcessRequest) (int, error) { 600 if ht.host == nil { 601 return cmd.ExecInShimHost(ctx, req) 602 } 603 return cmd.ExecInUvm(ctx, ht.host, req) 604} 605 606func (ht *hcsTask) DumpGuestStacks(ctx context.Context) string { 607 if ht.host != nil { 608 stacks, err := ht.host.DumpStacks(ctx) 609 if err != nil { 610 log.G(ctx).WithError(err).Warn("failed to capture guest stacks") 611 } else { 612 return stacks 613 } 614 } 615 return "" 616} 617 618func (ht *hcsTask) Share(ctx context.Context, req *shimdiag.ShareRequest) error { 619 if ht.host == nil { 620 return errTaskNotIsolated 621 } 622 // For hyper-v isolated WCOW the task used isn't the standard hcsTask so we 623 // only have to deal with the LCOW case here. 624 st, err := os.Stat(req.HostPath) 625 if err != nil { 626 return fmt.Errorf("could not open '%s' path on host: %s", req.HostPath, err) 627 } 628 var ( 629 hostPath string = req.HostPath 630 restrictAccess bool 631 fileName string 632 allowedNames []string 633 ) 634 if !st.IsDir() { 635 hostPath, fileName = filepath.Split(hostPath) 636 allowedNames = append(allowedNames, fileName) 637 restrictAccess = true 638 } 639 _, err = ht.host.AddPlan9(ctx, hostPath, req.UvmPath, req.ReadOnly, restrictAccess, allowedNames) 640 return err 641} 642 643func hcsPropertiesToWindowsStats(props *hcsschema.Properties) *stats.Statistics_Windows { 644 wcs := &stats.Statistics_Windows{Windows: &stats.WindowsContainerStatistics{}} 645 if props.Statistics != nil { 646 wcs.Windows.Timestamp = props.Statistics.Timestamp 647 wcs.Windows.ContainerStartTime = props.Statistics.ContainerStartTime 648 wcs.Windows.UptimeNS = props.Statistics.Uptime100ns * 100 649 if props.Statistics.Processor != nil { 650 wcs.Windows.Processor = &stats.WindowsContainerProcessorStatistics{ 651 TotalRuntimeNS: props.Statistics.Processor.TotalRuntime100ns * 100, 652 RuntimeUserNS: props.Statistics.Processor.RuntimeUser100ns * 100, 653 RuntimeKernelNS: props.Statistics.Processor.RuntimeKernel100ns * 100, 654 } 655 } 656 if props.Statistics.Memory != nil { 657 wcs.Windows.Memory = &stats.WindowsContainerMemoryStatistics{ 658 MemoryUsageCommitBytes: props.Statistics.Memory.MemoryUsageCommitBytes, 659 MemoryUsageCommitPeakBytes: props.Statistics.Memory.MemoryUsageCommitPeakBytes, 660 MemoryUsagePrivateWorkingSetBytes: props.Statistics.Memory.MemoryUsagePrivateWorkingSetBytes, 661 } 662 } 663 if props.Statistics.Storage != nil { 664 wcs.Windows.Storage = &stats.WindowsContainerStorageStatistics{ 665 ReadCountNormalized: props.Statistics.Storage.ReadCountNormalized, 666 ReadSizeBytes: props.Statistics.Storage.ReadSizeBytes, 667 WriteCountNormalized: props.Statistics.Storage.WriteCountNormalized, 668 WriteSizeBytes: props.Statistics.Storage.WriteSizeBytes, 669 } 670 } 671 } 672 return wcs 673} 674 675func (ht *hcsTask) Stats(ctx context.Context) (*stats.Statistics, error) { 676 s := &stats.Statistics{} 677 678 props, err := ht.c.PropertiesV2(ctx, hcsschema.PTStatistics) 679 if err != nil { 680 return nil, err 681 } 682 if ht.isWCOW { 683 s.Container = hcsPropertiesToWindowsStats(props) 684 } else { 685 s.Container = &stats.Statistics_Linux{Linux: props.Metrics} 686 } 687 if ht.ownsHost && ht.host != nil { 688 vmStats, err := ht.host.Stats(ctx) 689 if err != nil { 690 return nil, err 691 } 692 s.VM = vmStats 693 } 694 return s, nil 695} 696