| // Copyright 2020 The Monogon Project Authors. |
| // |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // 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 logtree |
| |
| import "source.monogon.dev/metropolis/pkg/logbuffer" |
| |
| // entry is a journal entry, representing a single log event (encompassed in a Payload) at a given DN. |
| // See the journal struct for more information about the global/local linked lists. |
| type entry struct { |
| // origin is the DN at which the log entry was recorded, or conversely, in which DN it will be available at. |
| origin DN |
| // journal is the parent journal of this entry. An entry can belong only to a single journal. This pointer is used |
| // to mutate the journal's head/tail pointers when unlinking an entry. |
| journal *journal |
| // leveled is the leveled log entry for this entry, if this log entry was emitted by leveled logging. Otherwise it |
| // is nil. |
| leveled *LeveledPayload |
| // raw is the raw log entry for this entry, if this log entry was emitted by raw logging. Otherwise it is nil. |
| raw *logbuffer.Line |
| |
| // prevGlobal is the previous entry in the global linked list, or nil if this entry is the oldest entry in the |
| // global linked list. |
| prevGlobal *entry |
| // nextGlobal is the next entry in the global linked list, or nil if this entry is the newest entry in the global |
| // linked list. |
| nextGlobal *entry |
| |
| // prevLocal is the previous entry in this entry DN's local linked list, or nil if this entry is the oldest entry in |
| // this local linked list. |
| prevLocal *entry |
| // prevLocal is the next entry in this entry DN's local linked list, or nil if this entry is the newest entry in |
| // this local linked list. |
| nextLocal *entry |
| |
| // seqLocal is a counter within a local linked list that increases by one each time a new log entry is added. It is |
| // used to quickly establish local linked list sizes (by subtracting seqLocal from both ends). This setup allows for |
| // O(1) length calculation for local linked lists as long as entries are only unlinked from the head or tail (which |
| // is the case in the current implementation). |
| seqLocal uint64 |
| } |
| |
| // external returns a LogEntry object for this entry, ie. the public version of this object, without fields relating to |
| // the parent journal, linked lists, sequences, etc. These objects are visible to library consumers. |
| func (e *entry) external() *LogEntry { |
| return &LogEntry{ |
| DN: e.origin, |
| Leveled: e.leveled, |
| Raw: e.raw, |
| } |
| } |
| |
| // unlink removes this entry from both global and local linked lists, updating the journal's head/tail pointers if |
| // needed. |
| // journal.mu must be taken as RW |
| func (e *entry) unlink() { |
| // Unlink from the global linked list. |
| if e.prevGlobal != nil { |
| e.prevGlobal.nextGlobal = e.nextGlobal |
| } |
| if e.nextGlobal != nil { |
| e.nextGlobal.prevGlobal = e.prevGlobal |
| } |
| // Update journal head/tail pointers. |
| if e.journal.head == e { |
| e.journal.head = e.prevGlobal |
| } |
| if e.journal.tail == e { |
| e.journal.tail = e.nextGlobal |
| } |
| |
| // Unlink from the local linked list. |
| if e.prevLocal != nil { |
| e.prevLocal.nextLocal = e.nextLocal |
| } |
| if e.nextLocal != nil { |
| e.nextLocal.prevLocal = e.prevLocal |
| } |
| // Update journal head/tail pointers. |
| if e.journal.heads[e.origin] == e { |
| e.journal.heads[e.origin] = e.prevLocal |
| } |
| if e.journal.tails[e.origin] == e { |
| e.journal.tails[e.origin] = e.nextLocal |
| } |
| } |
| |
| // quota describes the quota policy for logging at a given DN. |
| type quota struct { |
| // origin is the exact DN that this quota applies to. |
| origin DN |
| // max is the maximum count of log entries permitted for this DN - ie, the maximum size of the local linked list. |
| max uint64 |
| } |
| |
| // append adds an entry at the head of the global and local linked lists. |
| func (j *journal) append(e *entry) { |
| j.mu.Lock() |
| defer j.mu.Unlock() |
| |
| e.journal = j |
| |
| // Insert at head in global linked list, set pointers. |
| e.nextGlobal = nil |
| e.prevGlobal = j.head |
| if j.head != nil { |
| j.head.nextGlobal = e |
| } |
| j.head = e |
| if j.tail == nil { |
| j.tail = e |
| } |
| |
| // Create quota if necessary. |
| if _, ok := j.quota[e.origin]; !ok { |
| j.quota[e.origin] = "a{origin: e.origin, max: 8192} |
| } |
| |
| // Insert at head in local linked list, calculate seqLocal, set pointers. |
| e.nextLocal = nil |
| e.prevLocal = j.heads[e.origin] |
| if j.heads[e.origin] != nil { |
| j.heads[e.origin].nextLocal = e |
| e.seqLocal = e.prevLocal.seqLocal + 1 |
| } else { |
| e.seqLocal = 0 |
| } |
| j.heads[e.origin] = e |
| if j.tails[e.origin] == nil { |
| j.tails[e.origin] = e |
| } |
| |
| // Apply quota to the local linked list that this entry got inserted to, ie. remove elements in excess of the |
| // quota.max count. |
| quota := j.quota[e.origin] |
| count := (j.heads[e.origin].seqLocal - j.tails[e.origin].seqLocal) + 1 |
| if count > quota.max { |
| // Keep popping elements off the tail of the local linked list until quota is not violated. |
| left := count - quota.max |
| cur := j.tails[e.origin] |
| for { |
| // This shouldn't happen if quota.max >= 1. |
| if cur == nil { |
| break |
| } |
| if left == 0 { |
| break |
| } |
| el := cur |
| cur = el.nextLocal |
| // Unlinking the entry unlinks it from both the global and local linked lists. |
| el.unlink() |
| left -= 1 |
| } |
| } |
| } |