blob: 1580f5431de4bdc011ea28f68b118b050e0ce427 [file] [log] [blame]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package logtree
18
Serge Bazanski31370b02021-01-07 16:31:14 +010019import "source.monogon.dev/metropolis/pkg/logbuffer"
Serge Bazanskif68153c2020-10-26 13:54:37 +010020
Serge Bazanski216fe7b2021-05-21 18:36:16 +020021// entry is a journal entry, representing a single log event (encompassed in a
22// Payload) at a given DN. See the journal struct for more information about the
23// global/local linked lists.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020024type entry struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020025 // origin is the DN at which the log entry was recorded, or conversely, in which DN
26 // it will be available at.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020027 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020028 // journal is the parent journal of this entry. An entry can belong only to a
29 // single journal. This pointer is used to mutate the journal's head/tail pointers
30 // when unlinking an entry.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020031 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020032 // leveled is the leveled log entry for this entry, if this log entry was emitted
33 // by leveled logging. Otherwise it is nil.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020034 leveled *LeveledPayload
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035 // raw is the raw log entry for this entry, if this log entry was emitted by raw
36 // logging. Otherwise it is nil.
Serge Bazanskif68153c2020-10-26 13:54:37 +010037 raw *logbuffer.Line
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020038
Serge Bazanski216fe7b2021-05-21 18:36:16 +020039 // prevGlobal is the previous entry in the global linked list, or nil if this entry
40 // is the oldest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020041 prevGlobal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020042 // nextGlobal is the next entry in the global linked list, or nil if this entry is
43 // the newest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020044 nextGlobal *entry
45
Serge Bazanski216fe7b2021-05-21 18:36:16 +020046 // prevLocal is the previous entry in this entry DN's local linked list, or nil if
47 // this entry is the oldest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020048 prevLocal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020049 // prevLocal is the next entry in this entry DN's local linked list, or nil if this
50 // entry is the newest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020051 nextLocal *entry
52
Serge Bazanski216fe7b2021-05-21 18:36:16 +020053 // seqLocal is a counter within a local linked list that increases by one each time
54 // a new log entry is added. It is used to quickly establish local linked list
55 // sizes (by subtracting seqLocal from both ends). This setup allows for O(1)
56 // length calculation for local linked lists as long as entries are only unlinked
57 // from the head or tail (which is the case in the current implementation).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020058 seqLocal uint64
59}
60
Serge Bazanski216fe7b2021-05-21 18:36:16 +020061// external returns a LogEntry object for this entry, ie. the public version of
62// this object, without fields relating to the parent journal, linked lists,
63// sequences, etc. These objects are visible to library consumers.
Serge Bazanskif68153c2020-10-26 13:54:37 +010064func (e *entry) external() *LogEntry {
65 return &LogEntry{
66 DN: e.origin,
67 Leveled: e.leveled,
68 Raw: e.raw,
69 }
70}
71
Serge Bazanski216fe7b2021-05-21 18:36:16 +020072// unlink removes this entry from both global and local linked lists, updating the
73// journal's head/tail pointers if needed. journal.mu must be taken as RW
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020074func (e *entry) unlink() {
75 // Unlink from the global linked list.
76 if e.prevGlobal != nil {
77 e.prevGlobal.nextGlobal = e.nextGlobal
78 }
79 if e.nextGlobal != nil {
80 e.nextGlobal.prevGlobal = e.prevGlobal
81 }
82 // Update journal head/tail pointers.
83 if e.journal.head == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020084 e.journal.head = e.nextGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020085 }
86 if e.journal.tail == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020087 e.journal.tail = e.prevGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020088 }
89
90 // Unlink from the local linked list.
91 if e.prevLocal != nil {
92 e.prevLocal.nextLocal = e.nextLocal
93 }
94 if e.nextLocal != nil {
95 e.nextLocal.prevLocal = e.prevLocal
96 }
97 // Update journal head/tail pointers.
98 if e.journal.heads[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020099 e.journal.heads[e.origin] = e.nextLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200100 }
101 if e.journal.tails[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200102 e.journal.tails[e.origin] = e.prevLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200103 }
104}
105
106// quota describes the quota policy for logging at a given DN.
107type quota struct {
108 // origin is the exact DN that this quota applies to.
109 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200110 // max is the maximum count of log entries permitted for this DN - ie, the maximum
111 // size of the local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200112 max uint64
113}
114
115// append adds an entry at the head of the global and local linked lists.
116func (j *journal) append(e *entry) {
117 j.mu.Lock()
118 defer j.mu.Unlock()
119
120 e.journal = j
121
122 // Insert at head in global linked list, set pointers.
123 e.nextGlobal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200124 e.prevGlobal = j.tail
125 if j.tail != nil {
126 j.tail.nextGlobal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200127 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200128 j.tail = e
129 if j.head == nil {
130 j.head = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200131 }
132
133 // Create quota if necessary.
134 if _, ok := j.quota[e.origin]; !ok {
135 j.quota[e.origin] = &quota{origin: e.origin, max: 8192}
136 }
137
138 // Insert at head in local linked list, calculate seqLocal, set pointers.
139 e.nextLocal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200140 e.prevLocal = j.tails[e.origin]
141 if j.tails[e.origin] != nil {
142 j.tails[e.origin].nextLocal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200143 e.seqLocal = e.prevLocal.seqLocal + 1
144 } else {
145 e.seqLocal = 0
146 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200147 j.tails[e.origin] = e
148 if j.heads[e.origin] == nil {
149 j.heads[e.origin] = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200150 }
151
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200152 // Apply quota to the local linked list that this entry got inserted to, ie. remove
153 // elements in excess of the quota.max count.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200154 quota := j.quota[e.origin]
Serge Bazanski8fab0142023-03-29 16:48:16 +0200155 count := (j.tails[e.origin].seqLocal - j.heads[e.origin].seqLocal) + 1
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200156 if count > quota.max {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200157 // Keep popping elements off the head of the local linked list until quota is not
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200158 // violated.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200159 left := count - quota.max
Serge Bazanski8fab0142023-03-29 16:48:16 +0200160 cur := j.heads[e.origin]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200161 for {
162 // This shouldn't happen if quota.max >= 1.
163 if cur == nil {
164 break
165 }
166 if left == 0 {
167 break
168 }
169 el := cur
170 cur = el.nextLocal
171 // Unlinking the entry unlinks it from both the global and local linked lists.
172 el.unlink()
173 left -= 1
174 }
175 }
176}