寝ても覚めてもこんぴうた

プログラム書いたり、ネットワーク設計したり、サーバ構築したり、車いじったり、ゲームしたり。そんなひとにわたしはなりたい。 投げ銭は kyash_id : chidakiyo マデ

flutter-webやってみる

f:id:chidakiyo:20200106222849j:plain

ここ をベースに試してみます。

セットアップ

$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web

ここまで実行したらエディタを再起動しましょう

webが有効になると flutter devices コマンドの結果にChromeが出力されます(betaにしないと出てきません)

$ flutter devices
3 connected devices:

iPhone 11 Pro Max • 82AC52A8-9EC0-4495-B979-XXXXXXXXXXXX • ios            • com.apple.CoreSimulator.SimRuntime.iOS-13-3 (simulator)
Chrome            • chrome                               • web-javascript • Google Chrome 79.0.3945.88
Web Server        • web-server                           • web-javascript • Flutter Tools

また、IDEを再起動するとデバイスの一覧にChromeが表示されるようになります。

作成したアプリを実行する

すでに作成したアプリがある場合、以下のコマンドでwebとしてアプリを実行します。

flutter run -d chrome

おや、このままだと実行ができません。
webの設定がプロジェクトに反映されていないためなので以下のコマンドを実行し、既存のプロジェクトがwebとしても動作するようにします。

flutter create .

webディレクトリが作成されていればOKです。
再度以下のコマンドを実行しChromeで確認しましょう。

flutter run -d chrome

dartコードで開発をしつつ、ターミナルで r ボタンを押してChromeの画面を更新しつつ進めていくイメージだと思います。

WebのBuild

Webのbuildは以下のコマンドで実行します。

flutter build web

root/build/web 配下にhtmlやmain.dart.jsファイルなどが配置されます。

まとめ

Flutterで実装したアプリケーションがある場合、驚くほどかんたんにwebの画面を生成することができます。
デザイン的にもマテリアルデザインのものがきれいに出力されるので、手っ取り早くapp/webを両方作成したい人には良さそうです。

まだそれほど使い込んではいませんが、気づいたことなどはblogに残していこうと思います。

ではでは。

Goの標準機能で過去のバージョンをインストールする

f:id:chidakiyo:20191225141735j:plain

自分個人の環境はいつも最新のGoを利用しているので特にgoenvなどのようなバージョンを切り替えるツールを使っていないのですが、Goのバージョンを切り替えて使いたい人もいるみたい。

体感的にgoenvを利用している人が多そうではありますが、Go標準に複数のgoのバージョンをインストールする機能があるようなので、試してみます。

複数のgoバージョンでテストをしたいときなどに便利なのかもしれません。

内容としては ここ の公式ドキュメントの内容になります。

事前に必要なもの

  • goがインストールされていること

インストール方法

以下のようなコマンドで現在利用している以外のgoバージョンをインストールできます

$ go get golang.org/dl/go1.10.7

指定できるgoのバージョンは こちら に一覧があります。

別のGoバージョンの実行の仕方

インストールしたgoは以下のように実行ができます。

$ go1.10.7 version

別のバージョンの削除の仕方

$ go1.10.7 env

を実行し、GOROOTとして指定されているディレクトリと、実行した goX.Y.Z のバイナリを削除すればよいです。

ではでは!

GAE/goの東京リージョンからServerless VPC Access経由でMemorystoreへのアクセスをベンチマークしてみる

f:id:chidakiyo:20191211202210j:plain

つい先日、東京リージョンにも Serverless VPC Access がやってきたので、GAE/SEから Memorystore が利用できるようになりました。
Memorystoreは内部的にはRedisで、今までのGAE/SE 1st-genと呼ばれる環境ではmemcacheが提供されていましたが、2nd-genからはmemcacheが利用できないためMemorystoreを利用する必要がありました。

念願のMemorystoreが利用できるようになったのと、先日の こちらベンチマークを利用してGAE/GO環境からMemorystoreへのパフォーマンスを測定してみようと思います。

環境

  • GAE/go 1.13
  • インスタンスなどはデフォルトのまま
  • VPCは Min throughput: 200 Mbps、 Min throughput: 1000
  • リージョンはすべて東京

条件

  • redigo, go-redisの両方をテストしてみます。
  • redis の SET/GET の API 両方をベンチマークします

コード

以下のような感じです。

redigo

func put() testing.BenchmarkResult {

    conn := RedisPool.Get()
    defer conn.Close()

    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
        for i := 1; i <= b.N; i++ {
            _, err := conn.Do("SET", key, value)
            if err != nil {
                b.Fatal(err)
            }
        }
    })
    return result
}

func get() testing.BenchmarkResult {
    conn := RedisPool.Get()
    defer conn.Close()

    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
        for i := 1; i <= b.N; i++ {
            _, err := redis.String(conn.Do("GET", key))
            if err != nil {
                b.Fatal(err)
            }
        }
    })
    return result
}

go-Redis

func put() testing.BenchmarkResult {
    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
        for i := 1; i <= b.N; i++ {
            cmd := RedisClient.Set(key, value, 0)
            if cmd != nil && cmd.Err() != nil {
                b.Fatal(cmd.Err())
            }
        }
    })
    return result
}

func get() testing.BenchmarkResult {
    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
        for i := 1; i <= b.N; i++ {
            cmd := RedisClient.Get(key)
            if cmd != nil && cmd.Err() != nil {
                b.Fatal(cmd.Err())
            }
        }
    })
    return result
}

ベンチマークの実行

httpハンドラ経由で上記のベンチマークを実行し結果を取得します。

redigo

SET : 1383      1026929 ns/op
SET : 1086     1096002 ns/op
SET : 1076     1101032 ns/op

GET : 1237      974203 ns/op
GET : 1045     1134000 ns/op
GET : 952        1242443 ns/op

go-redis

SET : 986      1021182 ns/op
SET : 916        1185606 ns/op
SET : 1047     1066092 ns/op

GET : 1256      979747 ns/op
GET : 994        1145765 ns/op
GET : 1035     1082792 ns/op

事前にテストしたときは 800μsec だったのですが、
今日の計測ではSET/GETともに 1msec 程度のようでした。

まとめ

仕事ではAerospikeなど高速なKVSを使っているので、Redisで1msecだとちょっと遅いな、と感じちゃうところもありますが、GCPにでおまかせで使えると思えばすごく便利かも。

Memcacheが使えなくなってすごく困っていましたが、これならパフォーマンス気にせずガンガン使っていけそうです!

おわり。

Kindle fire 10 (19年モデル) を買ったのでGoogle Playをインストールする

f:id:chidakiyo:20191209164116j:plain

サイバーマンデーで10月頃に発売されたKIndle Fire HD 10が1万以下で出ていました。
子供がAmazon PrimeVideoを見ているとテレビを専有されるので子供のプライムビデオ用に買ってみました。

Kindle Fireを購入したのはこれで3台目で 7,8,10 インチとすべて揃った感じですが、
いままで買った全て Google Play をインストールするので惰性で今回もやってみます。

いろいろつないだりすると面倒なのでSilkブラウザだけで完結する方法を試してみます。

ちなみに以下の手順を試すのは 自己責任 です。ご注意ください

APKのインストールを許可する

「設定」 → 「セキュリティとプライバシー」 → 「不明アプリのインストール」

を選択します。

「Silkブラウザ」を選択し、このソースを許可のチェックを有効化します。

APKのダウンロード

以下のファイルを 順番に ダウンロードしてインストールします。

すべてダウンロードとインストールが完了するとGoogle Play Storeが利用できるようになります。

APKのインストールの許可を取り消す

先程Silkブラウザに不明アプリのインストールを許可したので、許可を取り消しておきましょう。
手順は先程と同じです。

これで、kindle firechromeなどがインストールできるようになりましたね。
以上です。

Goで特定の関数を持つか/実装されているかチェックする方法

f:id:chidakiyo:20191209094946j:plain

いまさらながら、なるほどーと思ったのでメモ。

go1.13のerrorsのUnwrap関数が以下のように実装されている

// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
// Otherwise, Unwrap returns nil.
func Unwrap(err error) error {
    u, ok := err.(interface {
        Unwrap() error
    })
    if !ok {
        return nil
    }
    return u.Unwrap()
}

error型を型アサーションして、okじゃなかったらUnwrapが実装されていないのでnilを返却する。
okだったらerrorのUnwrap関数を実行して返却する。

なるほどね。

GoのGraphQLライブラリのgqlgenのGetting startedをやってみた

f:id:chidakiyo:20191209014145j:plain

この記事は こちら をなぞってみた記事になります。
GraphQLをこれから始めたいという方におすすめ

gqlgenを利用してGraphQLサーバを実装する方法です。

  • todoのリストを返却する
  • 新しいtodoを作成する
  • 完了したtodoをマークする

ちなみにココからの手順はgo moduleを利用した方法になります。

プロジェクトの作成

プロジェクトのディレクトリを作成し、go moduleを初期化します

$ mkdir gqlgen-todos
$ cd gqlgen-todos
$ go mod init # 必要に応じてパスを指定

サーバを構築します

スキーマ定義します

qglgenはスキーマファーストのライブラリです。 コードを書く前に、GraphQLスキーマ定義言語を使用してAPIを記述します。 以下のようなschema.graphqlというファイルを作成し、initコマンドによってコードを生成します。

type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

type User {
  id: ID!
  name: String!
}

type Query {
  todos: [Todo!]!
}

input NewTodo {
  text: String!
  userId: String!
}

type Mutation {
  createTodo(input: NewTodo!): Todo!
}

プロジェクトスケルトンを作成する

以下のコマンドを実行します

$ go run github.com/99designs/gqlgen init

コマンドを実行すると

  • gqlgen.yml
  • generated.go
  • models_gen.go
  • resolver.go
  • server/server.go

のスケルトンが作成されます

データベースモデルの作成

Todo用に生成されたモデルは正しくありません。 ユーザが埋め込まれていますが、要求されたときのみ取得できるようにします。 そのため、代わりにtodo.goで新しいモデルを作成します。

package gqlgen_todos

type Todo struct {
    ID     string
    Text   string
    Done   bool
    UserID string
}

次に gqlgen.yml に以下を追加し、gqlgen を実行します。

models:
  Todo:
    model: github.com/[username]/gqlgen-todos.Todo

以下を実行し、再生成します

$ go run github.com/99designs/gqlgen -v

Note: -v フラグはgqlgenの動作を見るためにあります。

resolverを実装

ジェネレータが必要なresolverのインタフェースを生成しているので generated.go を確認します。

func NewExecutableSchema(cfg Config) graphql.ExecutableSchema {}
    // ...
}

type Config struct {
    Resolvers  ResolverRoot
    // ...
}

type ResolverRoot interface {
    Mutation() MutationResolver
    Query() QueryResolver
    Todo() TodoResolver
}

type MutationResolver interface {
    CreateTodo(ctx context.Context, input NewTodo) (*Todo, error)
}
type QueryResolver interface {
    Todos(ctx context.Context) ([]Todo, error)
}
type TodoResolver interface {
    User(ctx context.Context, obj *Todo) (*User, error)
}

~~

resolverを実装する

既存のコードを修正することができないため、 resolver.go を削除した後、 gqlgen を実行することで強制的に再実行します。

$ rm resolver.go
$ go run github.com/99designs/gqlgen

resolver.go の実装されていないところを埋めていきます。

package gqlgen_todos

import (
    context "context"
    "fmt"
    "math/rand"
)

type Resolver struct {
    todos []*Todo
}

func (r *Resolver) Mutation() MutationResolver {
    return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
    return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
    return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) {
    todo := &Todo{
        Text:   input.Text,
        ID:     fmt.Sprintf("T%d", rand.Int()),
        UserID: input.UserID,
    }
    r.todos = append(r.todos, todo)
    return todo, nil
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) Todos(ctx context.Context) ([]*Todo, error) {
    return r.todos, nil
}

type todoResolver struct{ *Resolver }

func (r *todoResolver) User(ctx context.Context, obj *Todo) (*User, error) {
    return &User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}

Resolver, CreateTodo, Todos, User 内を実装しています。

動作するようになったのでサーバを起動してみましょう

$ go run server/server.go

起動が成功したら、ブラウザで http://localhost:8080 へアクセスし、
以下のクエリを試してみましょう

これがmutationなので、todoを作成するクエリです

mutation createTodo {
  createTodo(input:{text:"todo", userId:"1"}) {
    user {
      id
    }
    text
    done
  }
}

以下のクエリで作成したtodoを取得できます

query findTodos {
    todos {
      text
      done
      user {
        name
      }
    }
}

仕上げ

resolver.go のpackageとimportの間に以下の行を追加します。

//go:generate go run github.com/99designs/gqlgen

このマジックコメントは、コードを再生成したいときに実行するコマンドになります。
プロジェクト全体で再帰的に生成を実行するには、以下のコマンドを使用します。

$ go generate ./...

いい感じですね!

Goでmain関数経由でもベンチマークする

f:id:chidakiyo:20191206192054j:plain

Goはテストの機能としてベンチマークを測定する機能が標準で提供されています。
ただ、そのままだとテストコードを実行できる環境でしか実行ができなかったりするため、 main関数から実行したい場合もあるかと思います。

今回はmainからベンチマークを実行できる機能を調べてみました。
Goが標準に提供しており、Appengine上で実行するなどの目的で使えます。

通常のベンチマーク

通常のテストコードから実行するベンチマークは以下のような感じですね

package main

import (
  "testing"
    "time"
)

func Benchmark_Sample(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        // 遅い処理
        time.Sleep(10 * time.Millisecond)
    }
}
go test -bench . -benchmem

という感じに実行すると以下のような結果が表示されます。

Benchmark_Sample-12          100          11565214 ns/op               4 B/op          0 allocs/op

見方はこの記事では特に説明しません。

mainから実行するベンチマーク

mainから実行する場合には testing.Benchmark が利用できます。
コードを見たほうが速いと思うので以下のようになります

package main

import (
    "testing"
    "time"
  "fmt"
)

func main() {
    result := testing.Benchmark(func(b *testing.B) {
        b.ResetTimer()
    for i := 0; i < b.N; i++ {
          // 遅い処理
          time.Sleep(10 * time.Millisecond)
      }
    })
    fmt.Printf("%s", result)
}

ほぼ見覚えのある形なので、特に違和感はないと思います。
テストとして実行するベンチマークと異なる点としては、 testing.Benchmark の戻り値の BenchmarkResult を利用してベンチマーク結果を取得するという点です。

BenchmarkResultString() 関数がよしなに実装されているので、適当にPrintしても以下のような感じで表示されます。

934     1275190 ns/op

その他の情報も細かく見たい人は、 BenchmarkResult の中の値を適切に表示すれば良いと思います。

まとめ

こんな感じで、テストコードとしてではなく、Goのmainから実行された処理の中でベンチマークが実行できるようになります。
おそらく嬉しい人は嬉しいのでは。

では、また。