зеркало из https://github.com/golang/playground.git
217 строки
4.9 KiB
Go
217 строки
4.9 KiB
Go
// Copyright 2014 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"time"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// When sandbox time begins.
|
|
var epoch = time.Unix(1257894000, 0)
|
|
|
|
// Recorder records the standard and error outputs of a sandbox program
|
|
// (comprised of playback headers) and converts it to a sequence of Events.
|
|
// It sanitizes each Event's Message to ensure it is valid UTF-8.
|
|
//
|
|
// Playground programs precede all their writes with a header (described
|
|
// below) that describes the time the write occurred (in playground time) and
|
|
// the length of the data that will be written. If a non-header is
|
|
// encountered where a header is expected, the output is scanned for the next
|
|
// header and the intervening text string is added to the sequence an event
|
|
// occurring at the same time as the preceding event.
|
|
//
|
|
// A playback header has this structure:
|
|
//
|
|
// 4 bytes: "\x00\x00PB", a magic header
|
|
// 8 bytes: big-endian int64, unix time in nanoseconds
|
|
// 4 bytes: big-endian int32, length of the next write
|
|
type Recorder struct {
|
|
stdout, stderr recorderWriter
|
|
}
|
|
|
|
func (r *Recorder) Stdout() io.Writer { return &r.stdout }
|
|
func (r *Recorder) Stderr() io.Writer { return &r.stderr }
|
|
|
|
type recorderWriter struct {
|
|
mu sync.Mutex
|
|
writes []byte
|
|
}
|
|
|
|
func (w *recorderWriter) bytes() []byte {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
return w.writes[0:len(w.writes):len(w.writes)]
|
|
}
|
|
|
|
func (w *recorderWriter) Write(b []byte) (n int, err error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
w.writes = append(w.writes, b...)
|
|
return len(b), nil
|
|
}
|
|
|
|
type Event struct {
|
|
Message string
|
|
Kind string // "stdout" or "stderr"
|
|
Delay time.Duration // time to wait before printing Message
|
|
}
|
|
|
|
func (r *Recorder) Events() ([]Event, error) {
|
|
stdout, stderr := r.stdout.bytes(), r.stderr.bytes()
|
|
|
|
evOut, err := decode("stdout", stdout)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
evErr, err := decode("stderr", stderr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
events := sortedMerge(evOut, evErr)
|
|
|
|
var (
|
|
out []Event
|
|
now = epoch
|
|
)
|
|
|
|
for _, e := range events {
|
|
delay := e.time.Sub(now)
|
|
if delay < 0 {
|
|
delay = 0
|
|
}
|
|
out = append(out, Event{
|
|
Message: string(sanitize(e.msg)),
|
|
Kind: e.kind,
|
|
Delay: delay,
|
|
})
|
|
if delay > 0 {
|
|
now = e.time
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
type event struct {
|
|
msg []byte
|
|
kind string
|
|
time time.Time
|
|
}
|
|
|
|
func decode(kind string, output []byte) ([]event, error) {
|
|
var (
|
|
magic = []byte{0, 0, 'P', 'B'}
|
|
headerLen = 8 + 4
|
|
last = epoch
|
|
events []event
|
|
)
|
|
add := func(t time.Time, b []byte) {
|
|
var prev *event
|
|
if len(events) > 0 {
|
|
prev = &events[len(events)-1]
|
|
}
|
|
if prev != nil && t.Equal(prev.time) {
|
|
// Merge this event with previous event, to avoid
|
|
// sending a lot of events for a big output with no
|
|
// significant timing information.
|
|
prev.msg = append(prev.msg, b...)
|
|
} else {
|
|
e := event{msg: b, kind: kind, time: t}
|
|
events = append(events, e)
|
|
}
|
|
last = t
|
|
}
|
|
for i := 0; i < len(output); {
|
|
if !bytes.HasPrefix(output[i:], magic) {
|
|
// Not a header; find next header.
|
|
j := bytes.Index(output[i:], magic)
|
|
if j < 0 {
|
|
// No more headers; bail.
|
|
add(last, output[i:])
|
|
break
|
|
}
|
|
add(last, output[i:i+j])
|
|
i += j
|
|
}
|
|
i += len(magic)
|
|
|
|
// Decode header.
|
|
if len(output)-i < headerLen {
|
|
return nil, errors.New("short header")
|
|
}
|
|
header := output[i : i+headerLen]
|
|
nanos := int64(binary.BigEndian.Uint64(header[0:]))
|
|
t := time.Unix(0, nanos)
|
|
if t.Before(last) {
|
|
// Force timestamps to be monotonic. (This could
|
|
// be an encoding error, which we ignore now but will
|
|
// likely be picked up when decoding the length.)
|
|
t = last
|
|
}
|
|
n := int(binary.BigEndian.Uint32(header[8:]))
|
|
if n < 0 {
|
|
return nil, fmt.Errorf("bad length: %v", n)
|
|
}
|
|
i += headerLen
|
|
|
|
// Slurp output.
|
|
// Truncated output is OK (probably caused by sandbox limits).
|
|
end := i + n
|
|
if end > len(output) {
|
|
end = len(output)
|
|
}
|
|
add(t, output[i:end])
|
|
i += n
|
|
}
|
|
return events, nil
|
|
}
|
|
|
|
// Sorted merge of two slices of events into one slice.
|
|
func sortedMerge(a, b []event) []event {
|
|
if len(a) == 0 {
|
|
return b
|
|
}
|
|
if len(b) == 0 {
|
|
return a
|
|
}
|
|
|
|
sorted := make([]event, 0, len(a)+len(b))
|
|
i, j := 0, 0
|
|
for i < len(a) && j < len(b) {
|
|
if a[i].time.Before(b[j].time) {
|
|
sorted = append(sorted, a[i])
|
|
i++
|
|
} else {
|
|
sorted = append(sorted, b[j])
|
|
j++
|
|
}
|
|
}
|
|
sorted = append(sorted, a[i:]...)
|
|
sorted = append(sorted, b[j:]...)
|
|
return sorted
|
|
}
|
|
|
|
// sanitize scans b for invalid utf8 code points. If found, it reconstructs
|
|
// the slice replacing the invalid codes with \uFFFD, properly encoded.
|
|
func sanitize(b []byte) []byte {
|
|
if utf8.Valid(b) {
|
|
return b
|
|
}
|
|
var buf bytes.Buffer
|
|
for len(b) > 0 {
|
|
r, size := utf8.DecodeRune(b)
|
|
b = b[size:]
|
|
buf.WriteRune(r)
|
|
}
|
|
return buf.Bytes()
|
|
}
|