blob: f805f5246ddb9b691aab1d09e5e9cb41ba6924ec [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// 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 database
import (
"bytes"
"fmt"
"sync"
"time"
"android.googlesource.com/platform/tools/gpu/binary"
"android.googlesource.com/platform/tools/gpu/log"
)
// Transient database wrapper. The wrapper allows transient stores to
// be stored in RAM rather than disk. A resource stored transiently
// must be loaded exactly once. After being loaded it is dropped from
// the store and another load will be an error. If the transient
// resource is never loaded it will eventually be purged.
// The transient database wrapper is used by the replay system where
// opcode streams are encoded as large byte streams which are normally
// used once with a few milliseconds of creation.
// How much to keep in RAM before trying to purge
const purgeSizeBytes = 100 << 10 // 100MB
// How many dropped timestamps to keep before trying to purge
const purgeDroppedCount = 100 // 100
// How many nanoseconds before it is safe to purge an item
const keepTimeNanoSeconds = int64(30) * int64(1000000000) // 30 seconds
func CreateTransientDatabase(inner Database) Database {
return &transient{inner: inner, entries: make(map[binary.ID][]byte), droppedAt: make(map[binary.ID]time.Time), storedAt: make(map[binary.ID]time.Time)}
}
type transient struct {
inner Database // database for non-transient resources
mutex sync.Mutex // held when modifying the maps or size
entries map[binary.ID][]byte // the transient resource data
droppedAt map[binary.ID]time.Time // the time the transient resource was dropped from the store
storedAt map[binary.ID]time.Time // the time the transient resource was stored in the store
size int // the total size of all the transient resources
}
func (t *transient) StoreLink(to, id binary.ID, logger log.Logger) error {
return fmt.Errorf("Can not StoreLink in a Transient Database")
}
func (t *transient) StoreRequest(request binary.Object, logger log.Logger) (id binary.ID, err error) {
return binary.ID{}, fmt.Errorf("Can not StoreRequest in a Transient Database")
}
// Drop a resource from the store. The t.mutex is already held.
func (t *transient) dropLocked(id binary.ID, logger log.Logger) {
if entry, ok := t.entries[id]; ok {
t.size -= len(entry)
delete(t.entries, id)
// Keep track of the fact that this id has been dropped.
now := time.Now()
t.droppedAt[id] = now
}
delete(t.storedAt, id)
}
// Store a resource in the transient store. If the store gets large purge old info.
func (t *transient) Store(r binary.Object, logger log.Logger) (id binary.ID, err error) {
logger = logger.Enter("Transient.Store")
defer func() { logger.Info("↪ id: %v, err: %v total_size: %v", id, err, t.size) }()
buf := &bytes.Buffer{}
enc := binary.NewEncoder(buf)
if err := r.Encode(enc); err != nil {
return binary.ID{}, err
}
data := buf.Bytes()
id = binary.NewID(data)
t.mutex.Lock()
now := time.Now()
// Purge any old items from the transient store, if it is large.
if t.size+len(data) > purgeSizeBytes {
for id, storedTime := range t.storedAt {
if now.Sub(storedTime).Nanoseconds() > keepTimeNanoSeconds {
t.dropLocked(id, logger)
if t.size <= purgeSizeBytes {
break
}
}
}
}
// Purge any old dropped markers from the store, if there are many.
if len(t.droppedAt) > purgeDroppedCount {
for id, droppedTime := range t.droppedAt {
if now.Sub(droppedTime).Nanoseconds() > keepTimeNanoSeconds {
delete(t.droppedAt, id)
if len(t.droppedAt) <= purgeDroppedCount {
break
}
}
}
}
t.entries[id] = data
t.storedAt[id] = now
t.size += len(data)
delete(t.droppedAt, id)
t.mutex.Unlock()
return id, nil
}
// Load a resource from the transient store. If not present in the transient store look in the
// underlying store. If in the transient store, drop it and return it.
func (t *transient) Load(id binary.ID, logger log.Logger, out binary.Object) (err error) {
logger = logger.Enter("Transient.Load")
logger.Info("(id: %v total_size: %v)", id, t.size)
defer func() { logger.Info("↪ err: %v total_size: %v", err, t.size) }()
t.mutex.Lock()
if entry, ok := t.entries[id]; ok {
t.mutex.Unlock()
d := binary.NewDecoder(bytes.NewBuffer(entry))
err := out.Decode(d)
t.mutex.Lock()
t.dropLocked(id, logger)
t.mutex.Unlock()
return err
} else {
if t, ok := t.droppedAt[id]; ok {
return fmt.Errorf("Id %v was dropped at %v", id, t)
}
t.mutex.Unlock()
return t.inner.Load(id, logger, out)
}
}
func (t *transient) Contains(id binary.ID, logger log.Logger) bool {
_, found := t.entries[id]
return found || t.inner.Contains(id, logger)
}
func (t *transient) Captures() (map[string]binary.ID, error) {
return nil, nil
}
func (t *transient) Close() {
t.inner.Close()
}