blob: c553ac8b7cdc889424b5eeab5acc379891519d35 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02002// SPDX-License-Identifier: Apache-2.0
Serge Bazanski5faa2fc2020-09-07 14:09:30 +02003
4package logtree
5
Tim Windelschmidt9f21f532024-05-07 15:14:20 +02006import "source.monogon.dev/osbase/logbuffer"
Serge Bazanskif68153c2020-10-26 13:54:37 +01007
Serge Bazanski216fe7b2021-05-21 18:36:16 +02008// entry is a journal entry, representing a single log event (encompassed in a
9// Payload) at a given DN. See the journal struct for more information about the
10// global/local linked lists.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020011type entry struct {
Serge Bazanski216fe7b2021-05-21 18:36:16 +020012 // origin is the DN at which the log entry was recorded, or conversely, in which DN
13 // it will be available at.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020014 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +020015 // journal is the parent journal of this entry. An entry can belong only to a
16 // single journal. This pointer is used to mutate the journal's head/tail pointers
17 // when unlinking an entry.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020018 journal *journal
Serge Bazanski216fe7b2021-05-21 18:36:16 +020019 // leveled is the leveled log entry for this entry, if this log entry was emitted
20 // by leveled logging. Otherwise it is nil.
Serge Bazanski1bfa0c22020-10-14 16:45:07 +020021 leveled *LeveledPayload
Serge Bazanski216fe7b2021-05-21 18:36:16 +020022 // raw is the raw log entry for this entry, if this log entry was emitted by raw
23 // logging. Otherwise it is nil.
Serge Bazanskif68153c2020-10-26 13:54:37 +010024 raw *logbuffer.Line
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020025
Serge Bazanski216fe7b2021-05-21 18:36:16 +020026 // prevGlobal is the previous entry in the global linked list, or nil if this entry
27 // is the oldest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020028 prevGlobal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020029 // nextGlobal is the next entry in the global linked list, or nil if this entry is
30 // the newest entry in the global linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020031 nextGlobal *entry
32
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033 // prevLocal is the previous entry in this entry DN's local linked list, or nil if
34 // this entry is the oldest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020035 prevLocal *entry
Serge Bazanski216fe7b2021-05-21 18:36:16 +020036 // prevLocal is the next entry in this entry DN's local linked list, or nil if this
37 // entry is the newest entry in this local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020038 nextLocal *entry
39
Serge Bazanski216fe7b2021-05-21 18:36:16 +020040 // seqLocal is a counter within a local linked list that increases by one each time
41 // a new log entry is added. It is used to quickly establish local linked list
42 // sizes (by subtracting seqLocal from both ends). This setup allows for O(1)
43 // length calculation for local linked lists as long as entries are only unlinked
44 // from the head or tail (which is the case in the current implementation).
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020045 seqLocal uint64
46}
47
Tim Windelschmidt9e964882025-07-22 01:06:21 +020048// defaultDNQuota defines how many messages should be stored per DN.
49const defaultDNQuota = 8192
50
Serge Bazanski216fe7b2021-05-21 18:36:16 +020051// external returns a LogEntry object for this entry, ie. the public version of
52// this object, without fields relating to the parent journal, linked lists,
53// sequences, etc. These objects are visible to library consumers.
Serge Bazanskif68153c2020-10-26 13:54:37 +010054func (e *entry) external() *LogEntry {
55 return &LogEntry{
56 DN: e.origin,
57 Leveled: e.leveled,
58 Raw: e.raw,
59 }
60}
61
Serge Bazanski216fe7b2021-05-21 18:36:16 +020062// unlink removes this entry from both global and local linked lists, updating the
63// journal's head/tail pointers if needed. journal.mu must be taken as RW
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020064func (e *entry) unlink() {
65 // Unlink from the global linked list.
66 if e.prevGlobal != nil {
67 e.prevGlobal.nextGlobal = e.nextGlobal
68 }
69 if e.nextGlobal != nil {
70 e.nextGlobal.prevGlobal = e.prevGlobal
71 }
72 // Update journal head/tail pointers.
73 if e.journal.head == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020074 e.journal.head = e.nextGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020075 }
76 if e.journal.tail == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020077 e.journal.tail = e.prevGlobal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020078 }
79
80 // Unlink from the local linked list.
81 if e.prevLocal != nil {
82 e.prevLocal.nextLocal = e.nextLocal
83 }
84 if e.nextLocal != nil {
85 e.nextLocal.prevLocal = e.prevLocal
86 }
87 // Update journal head/tail pointers.
88 if e.journal.heads[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020089 e.journal.heads[e.origin] = e.nextLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020090 }
91 if e.journal.tails[e.origin] == e {
Serge Bazanski8fab0142023-03-29 16:48:16 +020092 e.journal.tails[e.origin] = e.prevLocal
Serge Bazanski5faa2fc2020-09-07 14:09:30 +020093 }
94}
95
96// quota describes the quota policy for logging at a given DN.
97type quota struct {
98 // origin is the exact DN that this quota applies to.
99 origin DN
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200100 // max is the maximum count of log entries permitted for this DN - ie, the maximum
101 // size of the local linked list.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200102 max uint64
103}
104
105// append adds an entry at the head of the global and local linked lists.
106func (j *journal) append(e *entry) {
107 j.mu.Lock()
108 defer j.mu.Unlock()
109
110 e.journal = j
111
112 // Insert at head in global linked list, set pointers.
113 e.nextGlobal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200114 e.prevGlobal = j.tail
115 if j.tail != nil {
116 j.tail.nextGlobal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200117 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200118 j.tail = e
119 if j.head == nil {
120 j.head = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200121 }
122
123 // Create quota if necessary.
124 if _, ok := j.quota[e.origin]; !ok {
Tim Windelschmidt9e964882025-07-22 01:06:21 +0200125 j.quota[e.origin] = &quota{origin: e.origin, max: defaultDNQuota}
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200126 }
127
128 // Insert at head in local linked list, calculate seqLocal, set pointers.
129 e.nextLocal = nil
Serge Bazanski8fab0142023-03-29 16:48:16 +0200130 e.prevLocal = j.tails[e.origin]
131 if j.tails[e.origin] != nil {
132 j.tails[e.origin].nextLocal = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200133 e.seqLocal = e.prevLocal.seqLocal + 1
134 } else {
135 e.seqLocal = 0
136 }
Serge Bazanski8fab0142023-03-29 16:48:16 +0200137 j.tails[e.origin] = e
138 if j.heads[e.origin] == nil {
139 j.heads[e.origin] = e
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200140 }
141
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200142 // Apply quota to the local linked list that this entry got inserted to, ie. remove
143 // elements in excess of the quota.max count.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200144 quota := j.quota[e.origin]
Serge Bazanski8fab0142023-03-29 16:48:16 +0200145 count := (j.tails[e.origin].seqLocal - j.heads[e.origin].seqLocal) + 1
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200146 if count > quota.max {
Serge Bazanski8fab0142023-03-29 16:48:16 +0200147 // Keep popping elements off the head of the local linked list until quota is not
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200148 // violated.
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200149 left := count - quota.max
Serge Bazanski8fab0142023-03-29 16:48:16 +0200150 cur := j.heads[e.origin]
Serge Bazanski5faa2fc2020-09-07 14:09:30 +0200151 for {
152 // This shouldn't happen if quota.max >= 1.
153 if cur == nil {
154 break
155 }
156 if left == 0 {
157 break
158 }
159 el := cur
160 cur = el.nextLocal
161 // Unlinking the entry unlinks it from both the global and local linked lists.
162 el.unlink()
163 left -= 1
164 }
165 }
166}