Skip to content

Commit

Permalink
Add Stadia Support.
Browse files Browse the repository at this point in the history
  • Loading branch information
AWoloszyn committed Apr 29, 2019
1 parent 4f00997 commit a78cbd8
Show file tree
Hide file tree
Showing 26 changed files with 1,258 additions and 13 deletions.
8 changes: 8 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

# Description:
#
# Gapid is a graphics API debugger.

licenses(["notice"]) # Apache 2.0

exports_files(["LICENSE"])

load("@bazel_gazelle//:def.bzl", "gazelle")
load("//tools/build:rules.bzl", "copy_to")

Expand Down
1 change: 1 addition & 0 deletions cmd/gapis/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ go_library(
"//core/log:go_default_library",
"//core/os/android/adb:go_default_library",
"//core/os/device/bind:go_default_library",
"//core/os/device/ggp:go_default_library",
"//core/os/device/host:go_default_library",
"//core/os/device/remotessh:go_default_library",
"//core/os/file:go_default_library",
Expand Down
23 changes: 23 additions & 0 deletions cmd/gapis/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/google/gapid/core/log"
"github.com/google/gapid/core/os/android/adb"
"github.com/google/gapid/core/os/device/bind"
"github.com/google/gapid/core/os/device/ggp"
"github.com/google/gapid/core/os/device/host"
"github.com/google/gapid/core/os/device/remotessh"
"github.com/google/gapid/core/os/file"
Expand Down Expand Up @@ -128,6 +129,9 @@ func run(ctx context.Context) error {
crash.Go(func() { monitorRemoteSSHDevices(ctx, r, wg.Done) })
}

wg.Add(1)
crash.Go(func() { monitorGgpDevices(ctx, r, wg.Done) })

deviceScanDone, onDeviceScanDone := task.NewSignal()
crash.Go(func() {
wg.Wait()
Expand Down Expand Up @@ -201,6 +205,25 @@ func monitorRemoteSSHDevices(ctx context.Context, r *bind.Registry, scanDone fun
}
}

func monitorGgpDevices(ctx context.Context, r *bind.Registry, scanDone func()) {

func() {
// Populate the registry with all the existing devices.
defer scanDone() // Signal that we have a primed registry.

if devs, err := ggp.Devices(ctx); err == nil {
for _, d := range devs {
r.AddDevice(ctx, d)
r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr))
}
}
}()

if err := ggp.Monitor(ctx, r, time.Second*15); err != nil {
log.W(ctx, "Could not scan for remote SSH devices. Error: %v", err)
}
}

func loadStrings(ctx context.Context) []*stringtable.StringTable {
files, err := filepath.Glob(filepath.Join(*stringsPath, "*.stb"))
if err != nil {
Expand Down
33 changes: 33 additions & 0 deletions core/os/device/ggp/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (C) 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "go_default_library",
srcs = [
"device.go",
"parse.go",
],
importpath = "github.com/google/gapid/core/os/device/ggp",
visibility = ["//visibility:public"],
deps = [
"//core/event/task:go_default_library",
"//core/log:go_default_library",
"//core/os/device/bind:go_default_library",
"//core/os/device/remotessh:go_default_library",
"//core/os/file:go_default_library",
"//core/os/shell:go_default_library",
],
)
284 changes: 284 additions & 0 deletions core/os/device/ggp/device.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
// Copyright (C) 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ggp

import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"sync"
"time"

"github.com/google/gapid/core/event/task"
"github.com/google/gapid/core/log"
"github.com/google/gapid/core/os/device/bind"
"github.com/google/gapid/core/os/device/remotessh"
"github.com/google/gapid/core/os/file"
"github.com/google/gapid/core/os/shell"
)

// Binding represents an attached ggp ssh client
type Binding struct {
remotessh.Device
Gamelet string
}

const (
// Frequency at which to print scan errors
printScanErrorsEveryNSeconds = 120
)

var _ bind.Device = &Binding{}

var (
// Registry of all the discovered devices.
registry = bind.NewRegistry()

// cache is a map of device names to fully resolved bindings.
cache = map[string]*Binding{}
cacheMutex sync.Mutex // Guards cache.
)

// GGP is the path to the ggp executable.
var GGP file.Path

// GgpExecutablePath returns the path to the ggp
// executable
func GgpExecutablePath() (file.Path, error) {
if !GGP.IsEmpty() {
return GGP, nil
}
ggpExe := "ggp"
search := []string{ggpExe}

if ggpSDKPath := os.Getenv("GGP_SDK_PATH"); ggpSDKPath != "" {
search = append(search,
filepath.Join(ggpSDKPath, "dev", "bin", ggpExe))
}
for _, path := range search {
if p, err := file.FindExecutable(path); err == nil {
GGP = p
return GGP, nil
}
}

return file.Path{}, fmt.Errorf(
"ggp could not be found from GGP_SDK_PATH or PATH\n"+
"GGP_SDK_PATH: %v\n"+
"PATH: %v\n"+
"search: %v",
os.Getenv("GGP_SDK_PATH"), os.Getenv("PATH"), search)
}

func ggpDefaultRootConfigPath() (string, error) {
if runtime.GOOS == "windows" {
return os.Getenv("APPDATA"), nil
}
if p := os.Getenv("XDG_CONFIG_HOME"); p != "" {
return path.Clean(p), nil
}
if p := os.Getenv("HOME"); p != "" {
return path.Join(path.Clean(p), ".config"), nil
}
return "", fmt.Errorf("Can not find environment")
}

type GGPConfiguration struct {
remotessh.Configuration
Gamelet string
}

func getConfigs(ctx context.Context) ([]GGPConfiguration, error) {
configs := []GGPConfiguration{}

ggpPath, err := GgpExecutablePath()
if err != nil {
return nil, log.Errf(ctx, err, "Could not find ggp executable to list gamelets")
}
cli := ggpPath.System()

cmd := shell.Command(cli, "gamelet", "list")
gameletListOutBuf := &bytes.Buffer{}
gameletListErrBuf := &bytes.Buffer{}

if err := cmd.Capture(gameletListOutBuf, gameletListErrBuf).Run(ctx); err != nil {
return nil, err
}

t, err := ParseListOutput(gameletListOutBuf)
if err != nil {
return nil, log.Errf(ctx, err, "parse gamelet list")
}
for _, inf := range t.Rows {
if inf[3] != "RESERVED" && inf[3] != "IN_USE" {
continue
}
sshInitOutBuf := &bytes.Buffer{}
sshInitErrBuf := &bytes.Buffer{}

if err := shell.Command(cli, "ssh", "init", "--gamelet", inf[1], "-s").Capture(sshInitOutBuf, sshInitErrBuf).Run(ctx); err != nil {
log.W(ctx, "'ggp ssh init --gamelet %v -s' finished with error: %v", inf[1], err)
continue
}
if sshInitErrBuf.Len() != 0 {
log.W(ctx, "'ggp ssh init --gamelet %v -s' finished with error: %v", inf[1], sshInitErrBuf.String())
continue
}
envs := []string{
"YETI_DISABLE_GUEST_ORC=1",
"YETI_DISABLE_STREAMER=1",
}
cfg := remotessh.Configuration{
Name: inf[0],
Env: envs,
}
if err := json.Unmarshal(sshInitOutBuf.Bytes(), &cfg); err != nil {
log.W(ctx, "Failed at unmarshaling 'ggp ssh init --gamelet %v -s' output, fallback to use default config, err: %v", inf[1], err)
}
configs = append(configs, GGPConfiguration{cfg, inf[1]})
}
return configs, nil
}

// Monitor updates the registry with devices that are added and removed at the
// specified interval. Monitor returns once the context is cancelled.
func Monitor(ctx context.Context, r *bind.Registry, interval time.Duration) error {
unlisten := registry.Listen(bind.NewDeviceListener(r.AddDevice, r.RemoveDevice))
defer unlisten()

for _, d := range registry.Devices() {
r.AddDevice(ctx, d)
}

var lastErrorPrinted time.Time

for {
if err := func() error {
configs, err := getConfigs(ctx)
log.E(ctx, "Configs1: %v", configs)
if err != nil {
return err
}
if err := scanDevices(ctx, configs); err != nil {
if time.Since(lastErrorPrinted).Seconds() > printScanErrorsEveryNSeconds {
log.E(ctx, "Couldn't scan devices: %v", err)
lastErrorPrinted = time.Now()
}
} else {
lastErrorPrinted = time.Time{}
}
return nil
}(); err != nil {
return err
}

select {
case <-task.ShouldStop(ctx):
return nil
case <-time.After(interval):
}
}
}

// Devices returns the list of attached Ggp devices.
func Devices(ctx context.Context) ([]bind.Device, error) {
configs, err := getConfigs(ctx)
log.E(ctx, "Configs2: %v", configs)

if err != nil {
return nil, err
}

if err := scanDevices(ctx, configs); err != nil {
return nil, err
}
devs := registry.Devices()
out := make([]bind.Device, len(devs))
for i, d := range devs {
out[i] = d
}
return out, nil
}

func deviceStillConnected(ctx context.Context, d *Binding) bool {
return d.Status(ctx) == bind.Status_Online
}

func scanDevices(ctx context.Context, configurations []GGPConfiguration) error {
cacheMutex.Lock()
defer cacheMutex.Unlock()
allConfigs := make(map[string]bool)

for _, cfg := range configurations {
allConfigs[cfg.Name] = true

// If this device already exists, see if we
// can/have to remove it
if cached, ok := cache[cfg.Name]; ok {
if !deviceStillConnected(ctx, cached) {
delete(cache, cfg.Name)
registry.RemoveDevice(ctx, cached)
}
} else {
if device, err := remotessh.GetConnectedDevice(ctx, cfg.Configuration); err == nil {
dev := Binding{
Device: device,
Gamelet: cfg.Gamelet,
}
registry.AddDevice(ctx, dev)
cache[cfg.Name] = &dev
}
}
}

for name, dev := range cache {
if _, ok := allConfigs[name]; !ok {
delete(cache, name)
registry.RemoveDevice(ctx, *dev)
}
}
return nil
}

// On Ggp executables may not have the executable bit set
// So treat any file as executable
func (b Binding) ListExecutables(ctx context.Context, inPath string) ([]string, error) {
if inPath == "" {
inPath = b.GetURIRoot()
}
// 'find' may partially succeed. Redirect the error messages to /dev/null, only
// process the found files.
files, _ := b.Shell("find", `"`+inPath+`"`, "-mindepth", "1", "-maxdepth", "1", "-type", "f", "-printf", `%f\\n`, "2>/dev/null").Call(ctx)
scanner := bufio.NewScanner(strings.NewReader(files))
out := []string{}
for scanner.Scan() {
_, file := path.Split(scanner.Text())
out = append(out, file)
}
return out, nil
}

// DefaultReplayCacheDir returns the default replay resource cache directory
// on a GGP device
func (b Binding) DefaultReplayCacheDir() string {
return "/mnt/developer/ggp/gapid/replay_cache"
}
Loading

0 comments on commit a78cbd8

Please sign in to comment.