redisai

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2020 License: BSD-3-Clause Imports: 8 Imported by: 11

Documentation

Index

Examples

Constants

View Source
const (
	// BackendTF represents a TensorFlow backend
	BackendTF = string("TF")
	// BackendTorch represents a Torch backend
	BackendTorch = string("TORCH")
	// BackendONNX represents an ONNX backend
	BackendONNX = string("ORT")

	// DeviceCPU represents a CPU device
	DeviceCPU = string("CPU")
	// DeviceGPU represents a GPU device
	DeviceGPU = string("GPU")

	// TypeFloat represents a float type
	TypeFloat = string("FLOAT")
	// TypeDouble represents a double type
	TypeDouble = string("DOUBLE")
	// TypeInt8 represents a int8 type
	TypeInt8 = string("INT8")
	// TypeInt16 represents a int16 type
	TypeInt16 = string("INT16")
	// TypeInt32 represents a int32 type
	TypeInt32 = string("INT32")
	// TypeInt64 represents a int64 type
	TypeInt64 = string("INT64")
	// TypeUint8 represents a uint8 type
	TypeUint8 = string("UINT8")
	// TypeUint16 represents a uint16 type
	TypeUint16 = string("UINT16")
	// TypeFloat32 is an alias for float
	TypeFloat32 = string("FLOAT")
	// TypeFloat64 is an alias for double
	TypeFloat64 = string("DOUBLE")

	// TensorContentTypeBLOB is an alias for BLOB tensor content
	TensorContentTypeBlob = string("BLOB")

	// TensorContentTypeBLOB is an alias for BLOB tensor content
	TensorContentTypeValues = string("VALUES")

	// TensorContentTypeBLOB is an alias for BLOB tensor content
	TensorContentTypeMeta = string("META")
)

Variables

This section is empty.

Functions

func AddDagRunArgs added in v1.0.0

func AddDagRunArgs(loadKeys []string, persistKeys []string, commandArgs redis.Args) redis.Args

AddDagRunArgs for AI.DAGRUN and DAGRUN_RO commands.

func ProcessTensorGetReply added in v0.99.1

func ProcessTensorGetReply(reply interface{}, errIn error) (err error, dtype string, shape []int64, data interface{})

func ProcessTensorReplyValues

func ProcessTensorReplyValues(dtype string, reply interface{}) (data interface{}, err error)

func TensorGetTypeStrFromType added in v0.99.1

func TensorGetTypeStrFromType(dtype reflect.Type) (typestr string, err error)

Types

type AiClient added in v0.99.1

type AiClient interface {
	// Close ensures that no connection is kept alive and prior to that we flush all db commands
	Close() error
	DoOrSend(string, redis.Args, error) (interface{}, error)
}

type Client

type Client struct {
	Pool                  *redis.Pool
	PipelineActive        bool
	PipelineAutoFlushSize uint32
	PipelinePos           uint32
	ActiveConn            redis.Conn
}

Client is a RedisAI client

func Connect

func Connect(url string, pool *redis.Pool) (c *Client)

Connect establish an connection to the RedisAI Server.

If a pool `*redis.Pool` is passed then it will be used to connect to the server.

See the examples on how to connect with/without pool and on how to establish a secure SSL connection.

Example

Example of how to establish an connection from your app to the RedisAI Server

package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"log"
)

func main() {

	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	_ = client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// Get a tensor content as a slice of values
	// dt DataType, shape []int, data interface{}, err error
	// AI.TENSORGET foo VALUES
	_, _, fooTensorValues, err := client.TensorGetValues("foo")

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(fooTensorValues)
}
Output:

[1.1 2.2 3.3 4.4]
Example (Pool)

Example of how to establish an connection with a shared pool to the RedisAI Server

package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/gomodule/redigo/redis"
	"log"
)

func main() {

	host := "localhost:6379"
	password := ""
	pool := &redis.Pool{Dial: func() (redis.Conn, error) {
		return redis.Dial("tcp", host, redis.DialPassword(password))
	}}

	// Create a client.
	client := redisai.Connect("", pool)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	_ = client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// Get a tensor content as a slice of values
	// dt DataType, shape []int, data interface{}, err error
	// AI.TENSORGET foo VALUES
	_, _, fooTensorValues, err := client.TensorGetValues("foo")

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(fooTensorValues)
}
Output:

[1.1 2.2 3.3 4.4]
Example (Ssl)

Example of how to establish an SSL connection from your app to the RedisAI Server

package main

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/gomodule/redigo/redis"
	"io/ioutil"
	"log"
	"os"
)

func main() {
	// Consider the following helper methods that provide us with the connection details (host and password)
	// and the paths for:
	//     tls_cert - A a X.509 certificate to use for authenticating the  server to connected clients, masters or cluster peers. The file should be PEM formatted
	//     tls_key - A a X.509 private key to use for authenticating the  server to connected clients, masters or cluster peers. The file should be PEM formatted
	//	   tls_cacert - A PEM encoded CA's certificate file
	host, password := getConnectionDetails()
	tlsready, tls_cert, tls_key, tls_cacert := getTLSdetails()

	// Skip if we dont have all files to properly connect
	if tlsready == false {
		return
	}

	// Load client cert
	cert, err := tls.LoadX509KeyPair(tls_cert, tls_key)
	if err != nil {
		log.Fatal(err)
	}

	// Load CA cert
	caCert, err := ioutil.ReadFile(tls_cacert)
	if err != nil {
		log.Fatal(err)
	}
	caCertPool := x509.NewCertPool()
	caCertPool.AppendCertsFromPEM(caCert)

	clientTLSConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		RootCAs:      caCertPool,
	}

	// InsecureSkipVerify controls whether a client verifies the
	// server's certificate chain and host name.
	// If InsecureSkipVerify is true, TLS accepts any certificate
	// presented by the server and any host name in that certificate.
	// In this mode, TLS is susceptible to man-in-the-middle attacks.
	// This should be used only for testing.
	clientTLSConfig.InsecureSkipVerify = true

	pool := &redis.Pool{Dial: func() (redis.Conn, error) {
		return redis.Dial("tcp", host,
			redis.DialPassword(password),
			redis.DialTLSConfig(clientTLSConfig),
			redis.DialUseTLS(true),
			redis.DialTLSSkipVerify(true),
		)
	}}

	// create a connection from Pool
	client := redisai.Connect("", pool)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	_ = client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// Get a tensor content as a slice of values
	// dt DataType, shape []int, data interface{}, err error
	// AI.TENSORGET foo VALUES
	_, _, fooTensorValues, err := client.TensorGetValues("foo")

	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(fooTensorValues)
}

func getConnectionDetails() (host string, password string) {
	value, exists := os.LookupEnv("REDISAI_TEST_HOST")
	host = "localhost:6379"
	password = ""
	valuePassword, existsPassword := os.LookupEnv("REDISAI_TEST_PASSWORD")
	if exists && value != "" {
		host = value
	}
	if existsPassword && valuePassword != "" {
		password = valuePassword
	}
	return
}

func getTLSdetails() (tlsready bool, tls_cert string, tls_key string, tls_cacert string) {
	tlsready = false
	value, exists := os.LookupEnv("TLS_CERT")
	if exists && value != "" {
		info, err := os.Stat(value)
		if os.IsNotExist(err) || info.IsDir() {
			return
		}
		tls_cert = value
	} else {
		return
	}
	value, exists = os.LookupEnv("TLS_KEY")
	if exists && value != "" {
		info, err := os.Stat(value)
		if os.IsNotExist(err) || info.IsDir() {
			return
		}
		tls_key = value
	} else {
		return
	}
	value, exists = os.LookupEnv("TLS_CACERT")
	if exists && value != "" {
		info, err := os.Stat(value)
		if os.IsNotExist(err) || info.IsDir() {
			return
		}
		tls_cacert = value
	} else {
		return
	}
	tlsready = true
	return
}
Output:

func (*Client) ActiveConnNX

func (c *Client) ActiveConnNX()

func (*Client) Close

func (c *Client) Close() (err error)

Close ensures that no connection is kept alive and prior to that we flush all db commands

func (*Client) DagRun added in v1.0.0

func (c *Client) DagRun(loadKeys []string, persistKeys []string, dagCommandInterface DagCommandInterface) ([]interface{}, error)

Direct acyclic graph of operations to run within RedisAI

func (*Client) DagRunRO added in v1.0.0

func (c *Client) DagRunRO(loadKeys []string, dagCommandInterface DagCommandInterface) ([]interface{}, error)

The command is a read-only variant of AI.DAGRUN

func (*Client) DisablePipeline

func (c *Client) DisablePipeline() (err error)

func (*Client) DoOrSend added in v0.99.1

func (c *Client) DoOrSend(cmdName string, args redis.Args, errIn error) (reply interface{}, err error)

func (*Client) Flush

func (c *Client) Flush() (err error)

func (*Client) Info added in v0.99.1

func (c *Client) Info(key string) (map[string]string, error)

Returns information about the execution a model or a script.

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"io/ioutil"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// read the model from file
	data, err := ioutil.ReadFile("./../tests/test_data/graph.pb")

	// set the model to RedisAI
	err = client.ModelSet("example-info", redisai.BackendTF, redisai.DeviceCPU, data, []string{"a", "b"}, []string{"mul"})
	// print the error (should be <nil>)
	fmt.Println(err)

	// set the input tensors
	err = client.TensorSet("a", redisai.TypeFloat32, []int64{1}, []float32{1.1})
	err = client.TensorSet("b", redisai.TypeFloat32, []int64{1}, []float32{4.4})

	// run the model
	err = client.ModelRun("example-info", []string{"a", "b"}, []string{"mul"})
	// print the error (should be <nil>)
	fmt.Println(err)

	// get the model run info
	info, err := client.Info("example-info")

	// one model runs
	fmt.Println(fmt.Sprintf("Total runs: %s", info["calls"]))

}
Output:

<nil>
<nil>
Total runs: 1

func (*Client) LoadBackend

func (c *Client) LoadBackend(backend_identifier string, location string) (err error)

func (*Client) ModelDel

func (c *Client) ModelDel(keyName string) (err error)

func (*Client) ModelGet

func (c *Client) ModelGet(keyName string) (data []interface{}, err error)

ModelGet gets a RedisAI model from the RedisAI server The reply will an array, containing at

  • position 0 the backend used by the model as a String
  • position 1 the device used to execute the model as a String
  • position 2 the model's tag as a String
  • position 3 a blob containing the serialized model (when called with the BLOB argument) as a String
Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"io/ioutil"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)
	data, _ := ioutil.ReadFile("./../tests/test_data/creditcardfraud.pb")
	err := client.ModelSet("financialNet", redisai.BackendTF, redisai.DeviceCPU, data, []string{"transaction", "reference"}, []string{"output"})

	// Print the error, which should be <nil> in case of sucessfull modelset
	fmt.Println(err)

	/////////////////////////////////////////////////////////////
	// The important part of ModelGet example starts here
	reply, err := client.ModelGet("financialNet")
	backend := reply[0]
	device := reply[1]
	// print the error (should be <nil>)
	fmt.Println(err)
	fmt.Println(backend, device)

}
Output:

<nil>
<nil>
TF CPU

func (*Client) ModelGetToModel added in v0.99.1

func (c *Client) ModelGetToModel(keyName string, modelIn ModelInterface) (err error)
Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/RedisAI/redisai-go/redisai/implementations"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Create a model
	model := implementations.NewModel("TF", "CPU")
	model.SetInputs([]string{"transaction", "reference"})
	model.SetOutputs([]string{"output"})

	// Read the model from file
	model.SetBlobFromFile("./../tests/test_data/creditcardfraud.pb")

	// Set the model to RedisAI so that we can afterwards test the modelget
	err := client.ModelSetFromModel("financialNet", model)
	// print the error (should be <nil>)
	fmt.Println(err)

	/////////////////////////////////////////////////////////////
	// The important part of ModelGetToModel example starts here
	// Create an empty load to store the model from RedisAI
	model1 := implementations.NewEmptyModel()
	err = client.ModelGetToModel("financialNet", model1)
	// print the error (should be <nil>)
	fmt.Println(err)

	// print the backend and device info of the model
	fmt.Println(model1.Backend(), model1.Device())

}
Output:

<nil>
<nil>
TF CPU

func (*Client) ModelRun

func (c *Client) ModelRun(name string, inputTensorNames, outputTensorNames []string) (err error)

ModelRun runs the model present in the keyName, with the input tensor names, and output tensor names

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"io/ioutil"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// read the model from file
	data, err := ioutil.ReadFile("./../tests/test_data/graph.pb")

	// set the model to RedisAI
	err = client.ModelSet("example-model", redisai.BackendTF, redisai.DeviceCPU, data, []string{"a", "b"}, []string{"mul"})
	// print the error (should be <nil>)
	fmt.Println(err)

	// set the input tensors
	err = client.TensorSet("a", redisai.TypeFloat32, []int64{1}, []float32{1.1})
	err = client.TensorSet("b", redisai.TypeFloat32, []int64{1}, []float32{4.4})

	// run the model
	err = client.ModelRun("example-model", []string{"a", "b"}, []string{"mul"})
	// print the error (should be <nil>)
	fmt.Println(err)

}
Output:

<nil>
<nil>

func (*Client) ModelSet

func (c *Client) ModelSet(keyName, backend, device string, data []byte, inputs, outputs []string) (err error)

ModelSet sets a RedisAI model from a blob

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"io/ioutil"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)
	data, _ := ioutil.ReadFile("./../tests/test_data/creditcardfraud.pb")
	err := client.ModelSet("financialNet", redisai.BackendTF, redisai.DeviceCPU, data, []string{"transaction", "reference"}, []string{"output"})

	// Print the error, which should be <nil> in case of sucessfull modelset
	fmt.Println(err)
}
Output:

<nil>

func (*Client) ModelSetFromModel added in v0.99.1

func (c *Client) ModelSetFromModel(keyName string, model ModelInterface) (err error)

ModelSet sets a RedisAI model from a structure that implements the ModelInterface

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/RedisAI/redisai-go/redisai/implementations"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Create a model
	model := implementations.NewModel("TF", "CPU")
	model.SetInputs([]string{"transaction", "reference"})
	model.SetOutputs([]string{"output"})
	model.SetBlobFromFile("./../tests/test_data/creditcardfraud.pb")

	err := client.ModelSetFromModel("financialNet", model)

	// Print the error, which should be <nil> in case of successful modelset
	fmt.Println(err)
}
Output:

<nil>

func (*Client) Pipeline

func (c *Client) Pipeline(PipelineAutoFlushAtSize uint32)

func (*Client) Receive

func (c *Client) Receive() (reply interface{}, err error)

Receive receives a single reply from the Redis server

func (*Client) ResetStat added in v0.99.1

func (c *Client) ResetStat(key string) (string, error)

Resets all statistics associated with the key

func (*Client) ScriptDel

func (c *Client) ScriptDel(name string) (err error)

func (*Client) ScriptGet

func (c *Client) ScriptGet(name string) (data map[string]string, err error)

func (*Client) ScriptRun

func (c *Client) ScriptRun(name string, fn string, inputs []string, outputs []string) (err error)

ScriptRun runs a RedisAI script

func (*Client) ScriptSet

func (c *Client) ScriptSet(name string, device string, script_source string) (err error)

ScriptSet sets a RedisAI script from a blob

func (*Client) ScriptSetWithTag added in v1.0.1

func (c *Client) ScriptSetWithTag(name string, device string, script_source string, tag string) (err error)

ScriptSetWithTag sets a RedisAI script from a blob with tag

func (*Client) SendAndIncr

func (c *Client) SendAndIncr(commandName string, args redis.Args) (err error)

func (*Client) TensorGet

func (c *Client) TensorGet(name, format string) (data []interface{}, err error)
Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	_ = client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// Get a tensor content as a slice of values
	// AI.TENSORGET foo VALUES
	fooTensorValues, err := client.TensorGet("foo", redisai.TensorContentTypeValues)

	fmt.Println(fooTensorValues, err)
}
Output:

[FLOAT [2 2] [1.1 2.2 3.3 4.4]] <nil>

func (*Client) TensorGetBlob

func (c *Client) TensorGetBlob(name string) (dt string, shape []int64, data []byte, err error)

TensorGetValues gets a tensor's values

func (*Client) TensorGetMeta

func (c *Client) TensorGetMeta(name string) (dt string, shape []int64, err error)

TensorGetValues gets a tensor's values

func (*Client) TensorGetToTensor added in v0.99.1

func (c *Client) TensorGetToTensor(name, format string, tensor TensorInterface) (err error)
Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/RedisAI/redisai-go/redisai/implementations"
)

func main() {
	// Create a client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	_ = client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// Get a tensor content as a slice of values
	// AI.TENSORGET foo VALUES
	// Allocate an empty tensor
	fooTensor := implementations.NewAiTensor()
	err := client.TensorGetToTensor("foo", redisai.TensorContentTypeValues, fooTensor)

	// Print the tensor data
	fmt.Println(fooTensor.Data(), err)
}
Output:

[1.1 2.2 3.3 4.4] <nil>

func (*Client) TensorGetValues

func (c *Client) TensorGetValues(name string) (dt string, shape []int64, data interface{}, err error)

TensorGetValues gets a tensor's values

func (*Client) TensorSet

func (c *Client) TensorSet(keyName, dt string, dims []int64, data interface{}) (err error)

TensorSet sets a tensor

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
)

func main() {
	// Create a simple client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	err := client.TensorSet("foo", redisai.TypeFloat, []int64{2, 2}, []float32{1.1, 2.2, 3.3, 4.4})

	// print the error (should be <nil>)
	fmt.Println(err)
}
Output:

<nil>

func (*Client) TensorSetFromTensor added in v0.99.1

func (c *Client) TensorSetFromTensor(keyName string, tensor TensorInterface) (err error)

TensorSet sets a tensor

Example
package main

import (
	"fmt"
	"github.com/RedisAI/redisai-go/redisai"
	"github.com/RedisAI/redisai-go/redisai/implementations"
)

func main() {
	// Create a simple client.
	client := redisai.Connect("redis://localhost:6379", nil)

	// Build a tensor
	tensor := implementations.NewAiTensor()
	tensor.SetShape([]int64{2, 2})
	tensor.SetData([]float32{1.1, 2.2, 3.3, 4.4})

	// Set a tensor
	// AI.TENSORSET foo FLOAT 2 2 VALUES 1.1 2.2 3.3 4.4
	err := client.TensorSetFromTensor("foo", tensor)

	// print the error (should be <nil>)
	fmt.Println(err)
}
Output:

<nil>

type Dag added in v1.0.0

type Dag struct {
	// contains filtered or unexported fields
}

func NewDag added in v1.0.0

func NewDag() *Dag

func (*Dag) FlatArgs added in v1.0.0

func (d *Dag) FlatArgs() (redis.Args, error)

func (*Dag) ModelRun added in v1.0.0

func (d *Dag) ModelRun(name string, inputTensorNames, outputTensorNames []string) DagCommandInterface

func (*Dag) ParseReply added in v1.0.0

func (d *Dag) ParseReply(reply interface{}, err error) ([]interface{}, error)

func (*Dag) TensorGet added in v1.0.0

func (d *Dag) TensorGet(name, format string) DagCommandInterface

func (*Dag) TensorSet added in v1.0.0

func (d *Dag) TensorSet(keyName, dt string, dims []int64, data interface{}) DagCommandInterface

type DagCommandInterface added in v1.0.0

type DagCommandInterface interface {
	TensorSet(keyName, dt string, dims []int64, data interface{}) DagCommandInterface
	TensorGet(name, format string) DagCommandInterface
	ModelRun(name string, inputTensorNames, outputTensorNames []string) DagCommandInterface
	FlatArgs() (redis.Args, error)
	ParseReply(reply interface{}, err error) ([]interface{}, error)
}

DagCommandInterface is an interface that represents the skeleton of DAG supported commands needed to map it to a RedisAI DAGRUN and DAGURN_RO commands

type ModelInterface added in v0.99.1

type ModelInterface interface {
	Outputs() []string
	SetOutputs(outputs []string)
	Inputs() []string
	SetInputs(inputs []string)
	Blob() []byte
	SetBlob(blob []byte)
	Device() string
	SetDevice(device string)
	Backend() string
	SetBackend(backend string)
	Tag() string
	SetTag(tag string)
}

ModelInterface is an interface that represents the skeleton of a model needed to map it to a RedisAI Model with the proper operations

type TensorInterface added in v0.99.1

type TensorInterface interface {

	// Shape returns the size - in each dimension - of the tensor.
	Shape() []int64

	SetShape(shape []int64)

	// NumDims returns the number of dimensions of the tensor.
	NumDims() int64

	// Len returns the number of elements in the tensor.
	Len() int64

	Dtype() reflect.Type

	// Data returns the underlying tensor data
	Data() interface{}
	SetData(interface{})
}

TensorInterface is an interface that represents the skeleton of a tensor ( n-dimensional array of numerical data ) needed to map it to a RedisAI Model with the proper operations

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL