rbuf

package module
v0.0.0-...-75b7858 Latest Latest
Warning

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

Go to latest
Published: Mar 14, 2019 License: MIT Imports: 3 Imported by: 33

README

rbuf: a circular ring buffer in Golang

type FixedSizeRingBuf struct:

  • is a fixed-size circular ring buffer. Yes, just what is says. This structure is only for bytes, as it was written to optimize I/O, but could be easily adapted to any other type.

  • We keep a pair of ping/pong buffers so that we can linearize the circular buffer into a contiguous slice if need be.

For efficiency, a FixedSizeRingBuf may be vastly preferred to a bytes.Buffer. The ReadWithoutAdvance(), Advance(), and Adopt() methods are all non-standard methods written for speed.

For an I/O heavy application, I have replaced bytes.Buffer with FixedSizeRingBuf and seen memory consumption go from 8GB to 25MB. Yes, that is a 300x reduction in memory footprint. Everything ran faster too.

Note that Bytes(), while inescapable at times, is expensive: avoid it if possible. If all you need is len(Bytes()), then it is better to use the FixedSizeRingBuf.Readable member directly. Bytes() is expensive because it may copy the back and then the front of a wrapped buffer A[Use] into A[1-Use] in order to get a contiguous, unwrapped, slice. If possible use ContigLen() first to get the size that can be read without copying, Read() that amount, and then Read() a second time -- to avoid the copy.

copyright (c) 2014, Jason E. Aten

license: MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AtomicFixedSizeRingBuf

type AtomicFixedSizeRingBuf struct {
	A   [2][]byte // a pair of ping/pong buffers. Only one is active.
	Use int       // which A buffer is in active use, 0 or 1
	N   int       // MaxViewInBytes, the size of A[0] and A[1] in bytes.
	Beg int       // start of data in A[Use]
	// contains filtered or unexported fields
}

AtomicFixedSizeRingBuf: see FixedSizeRingBuf for the full details; this is the same, just safe for current access (and thus paying the price of synchronization on each call as well.)

func NewAtomicFixedSizeRingBuf

func NewAtomicFixedSizeRingBuf(maxViewInBytes int) *AtomicFixedSizeRingBuf

constructor. NewAtomicFixedSizeRingBuf will allocate internally two buffers of size maxViewInBytes.

func (*AtomicFixedSizeRingBuf) Adopt

func (b *AtomicFixedSizeRingBuf) Adopt(me []byte)

Adopt(): non-standard.

For efficiency's sake, (possibly) take ownership of already allocated slice offered in me.

If me is large we will adopt it, and we will potentially then write to the me buffer. If we already have a bigger buffer, copy me into the existing buffer instead.

Side-effect: may change b.Use, among other internal state changes.

func (*AtomicFixedSizeRingBuf) Advance

func (b *AtomicFixedSizeRingBuf) Advance(n int)

Advance(): non-standard, but better than Next(), because we don't have to unwrap our buffer and pay the cpu time for the copy that unwrapping may need. Useful in conjuction/after ReadWithoutAdvance() above.

func (*AtomicFixedSizeRingBuf) AdvanceBytesTwo

func (b *AtomicFixedSizeRingBuf) AdvanceBytesTwo(tb TwoBuffers)

Purpose of BytesTwo() and AdvanceBytesTwo(): avoid extra copying of data.

AdvanceBytesTwo() takes a TwoBuffers as input, this must have been from a previous call to BytesTwo(); no intervening calls to Bytes() or Adopt() are allowed (or any other future routine or client data access that changes the internal data location or contents) can have been made.

After sanity checks, AdvanceBytesTwo() advances the internal buffer, effectively calling Advance( len(tb.First) + len(tb.Second)).

If intervening-calls that changed the buffers (other than appending data to the buffer) are detected, we will panic as a safety/sanity/ aid-to-debugging measure.

func (*AtomicFixedSizeRingBuf) Bytes

func (b *AtomicFixedSizeRingBuf) Bytes(makeCopy bool) []byte

Bytes() returns a slice of the contents of the unread portion of the buffer.

To avoid copying, see the companion BytesTwo() call.

Unlike the standard library Bytes() method (on bytes.Buffer for example), the result of the AtomicFixedSizeRingBuf::Bytes(true) is a completely new returned slice, so modifying that slice will have no impact on the contents of the internal ring.

Bytes(false) acts like the standard library bytes.Buffer::Bytes() call, in that it returns a slice which is backed by the buffer itself (so no copy is involved).

The largest slice Bytes ever returns is bounded above by the maxViewInBytes value used when calling NewAtomicFixedSizeRingBuf().

Possible side-effect: may modify b.Use, the buffer in use.

func (*AtomicFixedSizeRingBuf) BytesTwo

func (b *AtomicFixedSizeRingBuf) BytesTwo() TwoBuffers

BytesTwo returns all readable bytes, but in two separate slices, to avoid copying. The two slices are from the same buffer, but are not contiguous. Either or both may be empty slices.

func (*AtomicFixedSizeRingBuf) ContigLen

func (b *AtomicFixedSizeRingBuf) ContigLen() int

ContigLen gets the length of the largest read that we can provide to a contiguous slice without an extra linearizing copy of all bytes internally.

func (*AtomicFixedSizeRingBuf) Read

func (b *AtomicFixedSizeRingBuf) Read(p []byte) (n int, err error)

Read():

From bytes.Buffer.Read(): Read reads the next len(p) bytes from the buffer or until the buffer is drained. The return value n is the number of bytes read. If the buffer has no data to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.

from the description of the Reader interface,
   http://golang.org/pkg/io/#Reader

Reader is the interface that wraps the basic Read method.

Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch space during the call. If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more.

When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes, it returns the number of bytes read. It may return the (non-nil) error from the same call or return the error (and n == 0) from a subsequent call. An instance of this general case is that a Reader returning a non-zero number of bytes at the end of the input stream may return either err == EOF or err == nil. The next Read should return 0, EOF regardless.

Callers should always process the n > 0 bytes returned before considering the error err. Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF behaviors.

Implementations of Read are discouraged from returning a zero byte count with a nil error, and callers should treat that situation as a no-op.

func (*AtomicFixedSizeRingBuf) ReadAndMaybeAdvance

func (b *AtomicFixedSizeRingBuf) ReadAndMaybeAdvance(p []byte, doAdvance bool) (n int, err error)

func (*AtomicFixedSizeRingBuf) ReadFrom

func (b *AtomicFixedSizeRingBuf) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom avoids intermediate allocation and copies. ReadFrom() reads data from r until EOF or error. The return value n is the number of bytes read. Any error except io.EOF encountered during the read is also returned.

func (*AtomicFixedSizeRingBuf) ReadWithoutAdvance

func (b *AtomicFixedSizeRingBuf) ReadWithoutAdvance(p []byte) (n int, err error)

ReadWithoutAdvance(): if you want to Read the data and leave it in the buffer, so as to peek ahead for example.

func (*AtomicFixedSizeRingBuf) Readable

func (b *AtomicFixedSizeRingBuf) Readable() int

Readable() returns the number of bytes available for reading.

func (*AtomicFixedSizeRingBuf) Reset

func (b *AtomicFixedSizeRingBuf) Reset()

Reset quickly forgets any data stored in the ring buffer. The data is still there, but the ring buffer will ignore it and overwrite those buffers as new data comes in.

func (*AtomicFixedSizeRingBuf) Write

func (b *AtomicFixedSizeRingBuf) Write(p []byte) (n int, err error)

Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p).

Write doesn't modify b.User, so once a []byte is pinned with a call to Bytes(), it should remain valid even with additional calls to Write() that come after the Bytes() call.

func (*AtomicFixedSizeRingBuf) WriteTo

func (b *AtomicFixedSizeRingBuf) WriteTo(w io.Writer) (n int64, err error)

WriteTo avoids intermediate allocation and copies. WriteTo writes data to w until there's no more data to write or when an error occurs. The return value n is the number of bytes written. Any error encountered during the write is also returned.

type FixedSizeRingBuf

type FixedSizeRingBuf struct {
	A        [2][]byte // a pair of ping/pong buffers. Only one is active.
	Use      int       // which A buffer is in active use, 0 or 1
	N        int       // MaxViewInBytes, the size of A[0] and A[1] in bytes.
	Beg      int       // start of data in A[Use]
	Readable int       // number of bytes available to read in A[Use]
}

FixedSizeRingBuf:

a fixed-size circular ring buffer. Yes, just what is says.

We keep a pair of ping/pong buffers so that we can linearize
the circular buffer into a contiguous slice if need be.

For efficiency, a FixedSizeRingBuf may be vastly preferred to a bytes.Buffer. The ReadWithoutAdvance(), Advance(), and Adopt() methods are all non-standard methods written for speed.

For an I/O heavy application, I have replaced bytes.Buffer with FixedSizeRingBuf and seen memory consumption go from 8GB to 25MB. Yes, that is a 300x reduction in memory footprint. Everything ran faster too.

Note that Bytes(), while inescapable at times, is expensive: avoid it if possible. Instead it is better to use the FixedSizeRingBuf.Readable member to get the number of bytes available. Bytes() is expensive because it may copy the back and then the front of a wrapped buffer A[Use] into A[1-Use] in order to get a contiguous slice. If possible use ContigLen() first to get the size that can be read without copying, Read() that amount, and then Read() a second time -- to avoid the copy. See BytesTwo() for a method that does this for you.

func NewFixedSizeRingBuf

func NewFixedSizeRingBuf(maxViewInBytes int) *FixedSizeRingBuf

constructor. NewFixedSizeRingBuf will allocate internally two buffers of size maxViewInBytes.

func (*FixedSizeRingBuf) Adopt

func (b *FixedSizeRingBuf) Adopt(me []byte)

Adopt(): non-standard.

For efficiency's sake, (possibly) take ownership of already allocated slice offered in me.

If me is large we will adopt it, and we will potentially then write to the me buffer. If we already have a bigger buffer, copy me into the existing buffer instead.

func (*FixedSizeRingBuf) Advance

func (b *FixedSizeRingBuf) Advance(n int)

Advance(): non-standard, but better than Next(), because we don't have to unwrap our buffer and pay the cpu time for the copy that unwrapping may need. Useful in conjuction/after ReadWithoutAdvance() above.

func (*FixedSizeRingBuf) Avail

func (f *FixedSizeRingBuf) Avail() int

func (*FixedSizeRingBuf) Bytes

func (b *FixedSizeRingBuf) Bytes() []byte

from the standard library description of Bytes(): Bytes() returns a slice of the contents of the unread portion of the buffer. If the caller changes the contents of the returned slice, the contents of the buffer will change provided there

are no intervening method calls on the Buffer.

The largest slice Bytes ever returns is bounded above by the maxViewInBytes value used when calling NewFixedSizeRingBuf().

func (*FixedSizeRingBuf) BytesTwo

func (b *FixedSizeRingBuf) BytesTwo(makeCopy bool) (first []byte, second []byte)

BytesTwo returns all readable bytes, but in two separate slices, to avoid copying. The two slices are from the same buffer, but are not contiguous. Either or both may be empty slices.

func (*FixedSizeRingBuf) ContigLen

func (b *FixedSizeRingBuf) ContigLen() int

ContigLen gets the length of the largest read that we can provide to a contiguous slice without an extra linearizing copy of all bytes internally.

func (*FixedSizeRingBuf) DeleteMostRecentBytes

func (f *FixedSizeRingBuf) DeleteMostRecentBytes(n int)

DeleteMostRecentBytes trims back the last n bytes written.

func (*FixedSizeRingBuf) First

func (f *FixedSizeRingBuf) First() int

First returns the earliest index, or -1 if the ring is empty

func (*FixedSizeRingBuf) Kth

func (f *FixedSizeRingBuf) Kth(k int) int

Kth presents the contents of the ring as a strictly linear sequence, so the user doesn't need to think about modular arithmetic. Here k indexes from [0, f.Readable-1], assuming f.Avail() is greater than 0. Kth() returns an actual index where the logical k-th element, starting from f.Beg, resides. f.Beg itself lives at k = 0. If k is out of bounds, or the ring is empty, -1 is returned.

func (*FixedSizeRingBuf) Last

func (f *FixedSizeRingBuf) Last() int

Last returns the index of the last element, or -1 if the ring is empty.

func (*FixedSizeRingBuf) LegalPos

func (b *FixedSizeRingBuf) LegalPos() (a0, aLast, b0, bLast int)

LegalPos returns the legal index positions, [a0,aLast] and [b0,bLast] inclusive, where the [a0,aLast] holds the first FIFO ordered segment, and the [b0,bLast] holds the second ordered segment, if any. A position of -1 means the segment is not used, perhaps because b.Readable is zero, or because the second segment [b0,bLast] is not in use (when everything fits in the first [a0,aLast] segment).

func (*FixedSizeRingBuf) Nextpos

func (f *FixedSizeRingBuf) Nextpos(from int) int

Next returns the index of the element after from, or -1 if no more. returns -2 if erroneous input (bad from).

func (*FixedSizeRingBuf) Prevpos

func (f *FixedSizeRingBuf) Prevpos(from int) int

Prevpos returns the index of the element before from, or -1 if no more and from is the first in the ring. Returns -2 on bad from position.

func (*FixedSizeRingBuf) Read

func (b *FixedSizeRingBuf) Read(p []byte) (n int, err error)

Read():

from bytes.Buffer.Read(): Read reads the next len(p) bytes from the buffer or until the buffer is drained. The return value n is the number of bytes read. If the buffer has no data to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.

from the description of the Reader interface,
   http://golang.org/pkg/io/#Reader

Reader is the interface that wraps the basic Read method.

Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch space during the call. If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more.

When Read encounters an error or end-of-file condition after successfully reading n > 0 bytes, it returns the number of bytes read. It may return the (non-nil) error from the same call or return the error (and n == 0) from a subsequent call. An instance of this general case is that a Reader returning a non-zero number of bytes at the end of the input stream may return either err == EOF or err == nil. The next Read should return 0, EOF regardless.

Callers should always process the n > 0 bytes returned before considering the error err. Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF behaviors.

Implementations of Read are discouraged from returning a zero byte count with a nil error, and callers should treat that situation as a no-op.

func (*FixedSizeRingBuf) ReadAndMaybeAdvance

func (b *FixedSizeRingBuf) ReadAndMaybeAdvance(p []byte, doAdvance bool) (n int, err error)

func (*FixedSizeRingBuf) ReadFrom

func (b *FixedSizeRingBuf) ReadFrom(r io.Reader) (n int64, err error)

ReadFrom avoids intermediate allocation and copies. ReadFrom() reads data from r until EOF or error. The return value n is the number of bytes read. Any error except io.EOF encountered during the read is also returned.

func (*FixedSizeRingBuf) ReadWithoutAdvance

func (b *FixedSizeRingBuf) ReadWithoutAdvance(p []byte) (n int, err error)

ReadWithoutAdvance(): if you want to Read the data and leave it in the buffer, so as to peek ahead for example.

func (*FixedSizeRingBuf) Reset

func (b *FixedSizeRingBuf) Reset()

Reset quickly forgets any data stored in the ring buffer. The data is still there, but the ring buffer will ignore it and overwrite those buffers as new data comes in.

func (*FixedSizeRingBuf) Write

func (b *FixedSizeRingBuf) Write(p []byte) (n int, err error)

Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p).

func (*FixedSizeRingBuf) WriteAndMaybeOverwriteOldestData

func (b *FixedSizeRingBuf) WriteAndMaybeOverwriteOldestData(p []byte) (n int, err error)

WriteAndMaybeOverwriteOldestData always consumes the full buffer p, even if that means blowing away the oldest unread bytes in the ring to make room. In reality, only the last min(len(p),b.N) bytes of p will end up being written to the ring.

This allows the ring to act as a record of the most recent b.N bytes of data -- a kind of temporal LRU cache, so the speak. The linux kernel's dmesg ring buffer is similar.

func (*FixedSizeRingBuf) WriteTo

func (b *FixedSizeRingBuf) WriteTo(w io.Writer) (n int64, err error)

WriteTo avoids intermediate allocation and copies. WriteTo writes data to w until there's no more data to write or when an error occurs. The return value n is the number of bytes written. Any error encountered during the write is also returned.

type Float64RingBuf

type Float64RingBuf struct {
	A        []float64
	N        int // MaxView, the total size of A, whether or not in use.
	Beg      int // start of in-use data in A
	Readable int // number of float64 available in A (in use)
}

Float64RingBuf:

a fixed-size circular ring buffer of float64

func NewFloat64RingBuf

func NewFloat64RingBuf(maxViewItems int) *Float64RingBuf

constructor. NewFloat64RingBuf will allocate internally a slice of maxViewItems float64.

func (*Float64RingBuf) Adopt

func (b *Float64RingBuf) Adopt(me []float64)

Adopt(): non-standard.

For efficiency's sake, (possibly) take ownership of already allocated slice offered in me.

If me is large we will adopt it, and we will potentially then write to the me buffer. If we already have a bigger buffer, copy me into the existing buffer instead.

func (*Float64RingBuf) Advance

func (b *Float64RingBuf) Advance(n int)

Advance(): non-standard, but better than Next(), because we don't have to unwrap our buffer and pay the cpu time for the copy that unwrapping may need. Useful in conjuction/after ReadWithoutAdvance() above.

func (*Float64RingBuf) Earliest

func (b *Float64RingBuf) Earliest() (v float64, ok bool)

Earliest returns the earliest written value v. ok will be true unless the ring is empty, in which case ok will be false, and v will be zero.

func (*Float64RingBuf) ReadFloat64

func (b *Float64RingBuf) ReadFloat64(p []float64) (n int, err error)

ReadFloat64():

from bytes.Buffer.Read(): Read reads the next len(p) float64 pointers from the buffer or until the buffer is drained. The return value n is the number of bytes read. If the buffer has no data to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.

func (*Float64RingBuf) ReadWithoutAdvance

func (b *Float64RingBuf) ReadWithoutAdvance(p []float64) (n int, err error)

ReadWithoutAdvance(): if you want to Read the data and leave it in the buffer, so as to peek ahead for example.

func (*Float64RingBuf) Reset

func (b *Float64RingBuf) Reset()

Reset quickly forgets any data stored in the ring buffer. The data is still there, but the ring buffer will ignore it and overwrite those buffers as new data comes in.

func (*Float64RingBuf) TwoContig

func (b *Float64RingBuf) TwoContig(makeCopy bool) (first []float64, second []float64)

TwoContig returns all readable float64, but in two separate slices, to avoid copying. The two slices are from the same buffer, but are not contiguous. Either or both may be empty slices.

func (*Float64RingBuf) Values

func (b *Float64RingBuf) Values() []float64

Values returns all readable float64 in a single buffer. Calling this function might allocate a new buffer to store the elements contiguously.

func (*Float64RingBuf) Write

func (b *Float64RingBuf) Write(p []float64) (n int, err error)

Write writes len(p) float64 values from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p).

func (*Float64RingBuf) WriteAndMaybeOverwriteOldestData

func (b *Float64RingBuf) WriteAndMaybeOverwriteOldestData(p []float64) (n int, err error)

WriteAndMaybeOverwriteOldestData always consumes the full buffer p, even if that means blowing away the oldest unread bytes in the ring to make room. In reality, only the last min(len(p),b.N) bytes of p will end up being written to the ring.

This allows the ring to act as a record of the most recent b.N bytes of data -- a kind of temporal LRU cache, so the speak. The linux kernel's dmesg ring buffer is similar.

type PointerRingBuf

type PointerRingBuf struct {
	A        []interface{}
	N        int // MaxView, the total size of A, whether or not in use.
	Beg      int // start of in-use data in A
	Readable int // number of pointers available in A (in use)
}

PointerRingBuf:

a fixed-size circular ring buffer of interface{}

func NewPointerRingBuf

func NewPointerRingBuf(sliceN int) *PointerRingBuf

constructor. NewPointerRingBuf will allocate internally a slice of size sliceN

func (*PointerRingBuf) Adopt

func (b *PointerRingBuf) Adopt(me []interface{})

Adopt(): non-standard.

For efficiency's sake, (possibly) take ownership of already allocated slice offered in me.

If me is large we will adopt it, and we will potentially then write to the me buffer. If we already have a bigger buffer, copy me into the existing buffer instead.

func (*PointerRingBuf) Advance

func (b *PointerRingBuf) Advance(n int)

Advance(): non-standard, but better than Next(), because we don't have to unwrap our buffer and pay the cpu time for the copy that unwrapping may need. Useful in conjuction/after ReadWithoutAdvance() above.

func (*PointerRingBuf) Push

func (b *PointerRingBuf) Push(p []interface{}) (n int, err error)

Push writes len(p) pointers from p to the ring. It returns the number of elements written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Push must return a non-nil error if it returns n < len(p).

func (*PointerRingBuf) PushAndMaybeOverwriteOldestData

func (b *PointerRingBuf) PushAndMaybeOverwriteOldestData(p []interface{}) (n int, err error)

PushAndMaybeOverwriteOldestData always consumes the full slice p, even if that means blowing away the oldest unread pointers in the ring to make room. In reality, only the last min(len(p),b.N) bytes of p will end up being written to the ring.

This allows the ring to act as a record of the most recent b.N bytes of data -- a kind of temporal LRU cache, so the speak. The linux kernel's dmesg ring buffer is similar.

func (*PointerRingBuf) ReadPtrs

func (b *PointerRingBuf) ReadPtrs(p []interface{}) (n int, err error)

ReadPtrs():

from bytes.Buffer.Read(): Read reads the next len(p) interface{} pointers from the buffer or until the buffer is drained. The return value n is the number of bytes read. If the buffer has no data to return, err is io.EOF (unless len(p) is zero); otherwise it is nil.

func (*PointerRingBuf) ReadWithoutAdvance

func (b *PointerRingBuf) ReadWithoutAdvance(p []interface{}) (n int, err error)

ReadWithoutAdvance(): if you want to Read the data and leave it in the buffer, so as to peek ahead for example.

func (*PointerRingBuf) Reset

func (b *PointerRingBuf) Reset()

Reset quickly forgets any data stored in the ring buffer. The data is still there, but the ring buffer will ignore it and overwrite those buffers as new data comes in.

func (*PointerRingBuf) TwoContig

func (b *PointerRingBuf) TwoContig() (first []interface{}, second []interface{})

TwoContig returns all readable pointers, but in two separate slices, to avoid copying. The two slices are from the same buffer, but are not contiguous. Either or both may be empty slices.

func (*PointerRingBuf) WritePtrs

func (b *PointerRingBuf) WritePtrs(p []interface{}) (n int, err error)

WritePtrs writes len(p) interface{} values from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p).

type TwoBuffers

type TwoBuffers struct {
	First  []byte // the first part of the contents
	Second []byte // the second part of the contents
}

TwoBuffers: the return value of BytesTwo(). TwoBuffers holds two slices to the contents of the readable area of the internal buffer. The slices contents are logically ordered First then Second, but the Second will actually be physically before the First. Either or both of First and Second may be empty slices.

Jump to

Keyboard shortcuts

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