blob: 2a60aa1e98707581581d4eadea63b1483eb06db7 [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 Bazanski549b72b2021-01-07 14:54:19 +010019import "git.monogon.dev/source/nexantic.git/metropolis/pkg/logbuffer"
Serge Bazanskif68153c2020-10-26 13:54:37 +010020
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020021// entry is a journal entry, representing a single log event (encompassed in a Payload) at a given DN.
22// See the journal struct for more information about the global/local linked lists.
23type entry struct {
24 // origin is the DN at which the log entry was recorded, or conversely, in which DN it will be available at.
25 origin DN
26 // journal is the parent journal of this entry. An entry can belong only to a single journal. This pointer is used
27 // to mutate the journal's head/tail pointers when unlinking an entry.
28 journal *journal
Serge Bazanskif68153c2020-10-26 13:54:37 +010029 // leveled is the leveled log entry for this entry, if this log entry was emitted by leveled logging. Otherwise it
30 // is nil.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020031 leveled *LeveledPayload
Serge Bazanskif68153c2020-10-26 13:54:37 +010032 // raw is the raw log entry for this entry, if this log entry was emitted by raw logging. Otherwise it is nil.
33 raw *logbuffer.Line
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020034
35 // prevGlobal is the previous entry in the global linked list, or nil if this entry is the oldest entry in the
36 // global linked list.
37 prevGlobal *entry
38 // nextGlobal is the next entry in the global linked list, or nil if this entry is the newest entry in the global
39 // linked list.
40 nextGlobal *entry
41
42 // prevLocal is the previous entry in this entry DN's local linked list, or nil if this entry is the oldest entry in
43 // this local linked list.
44 prevLocal *entry
45 // prevLocal is the next entry in this entry DN's local linked list, or nil if this entry is the newest entry in
46 // this local linked list.
47 nextLocal *entry
48
49 // seqLocal is a counter within a local linked list that increases by one each time a new log entry is added. It is
50 // used to quickly establish local linked list sizes (by subtracting seqLocal from both ends). This setup allows for
51 // O(1) length calculation for local linked lists as long as entries are only unlinked from the head or tail (which
52 // is the case in the current implementation).
53 seqLocal uint64
54}
55
Serge Bazanskif68153c2020-10-26 13:54:37 +010056// external returns a LogEntry object for this entry, ie. the public version of this object, without fields relating to
57// the parent journal, linked lists, sequences, etc. These objects are visible to library consumers.
58func (e *entry) external() *LogEntry {
59 return &LogEntry{
60 DN: e.origin,
61 Leveled: e.leveled,
62 Raw: e.raw,
63 }
64}
65
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020066// unlink removes this entry from both global and local linked lists, updating the journal's head/tail pointers if
67// needed.
68// journal.mu must be taken as RW
69func (e *entry) unlink() {
70 // Unlink from the global linked list.
71 if e.prevGlobal != nil {
72 e.prevGlobal.nextGlobal = e.nextGlobal
73 }
74 if e.nextGlobal != nil {
75 e.nextGlobal.prevGlobal = e.prevGlobal
76 }
77 // Update journal head/tail pointers.
78 if e.journal.head == e {
79 e.journal.head = e.prevGlobal
80 }
81 if e.journal.tail == e {
82 e.journal.tail = e.nextGlobal
83 }
84
85 // Unlink from the local linked list.
86 if e.prevLocal != nil {
87 e.prevLocal.nextLocal = e.nextLocal
88 }
89 if e.nextLocal != nil {
90 e.nextLocal.prevLocal = e.prevLocal
91 }
92 // Update journal head/tail pointers.
93 if e.journal.heads[e.origin] == e {
94 e.journal.heads[e.origin] = e.prevLocal
95 }
96 if e.journal.tails[e.origin] == e {
97 e.journal.tails[e.origin] = e.nextLocal
98 }
99}
100
101// quota describes the quota policy for logging at a given DN.
102type quota struct {
103 // origin is the exact DN that this quota applies to.
104 origin DN
105 // max is the maximum count of log entries permitted for this DN - ie, the maximum size of the local linked list.
106 max uint64
107}
108
109// append adds an entry at the head of the global and local linked lists.
110func (j *journal) append(e *entry) {
111 j.mu.Lock()
112 defer j.mu.Unlock()
113
114 e.journal = j
115
116 // Insert at head in global linked list, set pointers.
117 e.nextGlobal = nil
118 e.prevGlobal = j.head
119 if j.head != nil {
120 j.head.nextGlobal = e
121 }
122 j.head = e
123 if j.tail == nil {
124 j.tail = e
125 }
126
127 // Create quota if necessary.
128 if _, ok := j.quota[e.origin]; !ok {
129 j.quota[e.origin] = &quota{origin: e.origin, max: 8192}
130 }
131
132 // Insert at head in local linked list, calculate seqLocal, set pointers.
133 e.nextLocal = nil
134 e.prevLocal = j.heads[e.origin]
135 if j.heads[e.origin] != nil {
136 j.heads[e.origin].nextLocal = e
137 e.seqLocal = e.prevLocal.seqLocal + 1
138 } else {
139 e.seqLocal = 0
140 }
141 j.heads[e.origin] = e
142 if j.tails[e.origin] == nil {
143 j.tails[e.origin] = e
144 }
145
146 // Apply quota to the local linked list that this entry got inserted to, ie. remove elements in excess of the
147 // quota.max count.
148 quota := j.quota[e.origin]
149 count := (j.heads[e.origin].seqLocal - j.tails[e.origin].seqLocal) + 1
150 if count > quota.max {
151 // Keep popping elements off the tail of the local linked list until quota is not violated.
152 left := count - quota.max
153 cur := j.tails[e.origin]
154 for {
155 // This shouldn't happen if quota.max >= 1.
156 if cur == nil {
157 break
158 }
159 if left == 0 {
160 break
161 }
162 el := cur
163 cur = el.nextLocal
164 // Unlinking the entry unlinks it from both the global and local linked lists.
165 el.unlink()
166 left -= 1
167 }
168 }
169}