Skip to content

Commit 8d6b3a5

Browse files
authored
perf: improves performance and simplify the logger architecture
perf: improves casting method taking advantage of the buf.AvailableBuffer() method to append values instead of allocating new values perf: reduces the background goroutine updating the datetime from 3 a single one perf: removes 1 heap allocation from the datetime printer improving performances refactor: simplify components interaction injecting services into the encoders from the logger refactor: removes the colors printer service merging it into a new service called Printer fix: enable the default encoder Color method to print dateTime, aligning it with the other encoders
1 parent 7ec81e1 commit 8d6b3a5

24 files changed

Lines changed: 559 additions & 516 deletions

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,12 @@ terminal graphical visualization time.
7171

7272
| Encoder | Configuration | ns/op | B/op | allocs/op |
7373
|---------------------|--------------------|-------|------|-----------|
74-
| **Default Encoder** | All Properties OFF | 484.0 | 80 | 1 |
75-
| | All Properties ON | 540.1 | 104 | 2 |
76-
| **JSON Encoder** | All Properties OFF | 507.3 | 80 | 1 |
77-
| | All Properties ON | 553.6 | 104 | 2 |
78-
| **YAML Encoder** | All Properties OFF | 531.3 | 80 | 1 |
79-
| | All Properties ON | 588.2 | 104 | 2 |
74+
| **Default Encoder** | All Properties OFF | 490.3 | 80 | 1 |
75+
| | All Properties ON | 511.2 | 104 | 1 |
76+
| **JSON Encoder** | All Properties OFF | 513.3 | 80 | 1 |
77+
| | All Properties ON | 536.5 | 104 | 1 |
78+
| **YAML Encoder** | All Properties OFF | 535.3 | 80 | 1 |
79+
| | All Properties ON | 557.1 | 104 | 1 |
8080

8181

8282
## 🤝 Contributing

internal/services/colors_printer.go

Lines changed: 0 additions & 34 deletions
This file was deleted.

internal/services/datetime_printer.go

Lines changed: 68 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -21,112 +21,108 @@ var timeFormat = map[s.DateTimeFormat]string{
2121
s.JP: "03:04:05 PM",
2222
}
2323

24+
var (
25+
dateTimePrinterInstance *DateTimePrinter
26+
dateTimePrinterOnce sync.Once
27+
)
28+
2429
type DateTimePrinter struct {
25-
timeNow func() time.Time // Function to get current time, allows mocking for tests
26-
currentFormat atomic.Value
27-
currentDate atomic.Value
28-
currentTime atomic.Value
29-
currentUnix atomic.Value
30-
dateOnce sync.Once
31-
timeOnce sync.Once
32-
unixOnce sync.Once
30+
timeNow func() time.Time
31+
cachedDates [3]atomic.Value
32+
cachedTimes [3]atomic.Value
33+
currentUnix atomic.Value
3334
}
3435

35-
// RetrieveDateTime returns the current date, time, and combined/unix string based on the configuration.
36-
// Returns: (dateString, timeString, combinedOrUnixString).
37-
// If the format is UnixTimestamp, the timestamp is returned as the third value, ignoring the boolean flags.
38-
// Otherwise, 'addDate' and 'addTime' control which components are generated.
39-
func (d *DateTimePrinter) RetrieveDateTime(addDate, addTime bool) (string, string, string) {
40-
var dateRes, timeRes string
41-
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)
42-
43-
if currentFmt == s.UnixTimestamp && (addDate || addTime) {
44-
d.unixOnce.Do(func() {
45-
d.currentUnix.Store(strconv.FormatInt(d.timeNow().Unix(), 10))
46-
go d.updateCurrentUnixEverySecond()
47-
})
48-
36+
// RetrieveDateTime now accepts the desired format
37+
func (d *DateTimePrinter) RetrieveDateTime(fmt s.DateTimeFormat, addDate, addTime bool) (string, string, string) {
38+
if fmt == s.UnixTimestamp {
4939
return "", "", d.currentUnix.Load().(string)
5040
}
5141

52-
if addDate {
53-
d.dateOnce.Do(func() {
54-
d.currentDate.Store(d.timeNow().Format(dateFormat[currentFmt]))
55-
go d.updateCurrentDateEveryDay()
56-
})
42+
var dateRes, timeRes string
5743

58-
dateRes = d.currentDate.Load().(string)
44+
if addDate {
45+
dateRes = d.cachedDates[fmt].Load().(string)
5946
}
6047

6148
if addTime {
62-
d.timeOnce.Do(func() {
63-
d.currentTime.Store(d.timeNow().Format(timeFormat[currentFmt]))
64-
go d.updateCurrentTimeEverySecond()
65-
})
66-
67-
timeRes = d.currentTime.Load().(string)
68-
}
69-
70-
if addDate && addTime {
71-
return "", "", dateRes + " " + timeRes
49+
timeRes = d.cachedTimes[fmt].Load().(string)
7250
}
7351

7452
return dateRes, timeRes, ""
7553
}
7654

77-
// UpdateDateTimeFormat updates the DateTimePrinter's currentFormat property and updates the currentDate and
78-
// currentTime properties accordingly.
79-
func (d *DateTimePrinter) UpdateDateTimeFormat(format s.DateTimeFormat) {
55+
// init initializes the current timestamp and cached formatted strings,
56+
// then starts background goroutines to keep them updated.
57+
func (d *DateTimePrinter) init() {
8058
now := d.timeNow()
59+
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))
60+
61+
for i := 0; i < 3; i++ {
62+
fmt := s.DateTimeFormat(i)
63+
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
64+
d.cachedTimes[i].Store(now.Format(timeFormat[fmt]))
65+
}
8166

82-
d.currentFormat.Store(format)
83-
d.currentDate.Store(now.Format(dateFormat[format]))
84-
d.currentTime.Store(now.Format(timeFormat[format]))
67+
go d.loopUpdateDate()
68+
go d.loopUpdateTime()
8569
}
8670

87-
// updateCurrentDateEveryDay synchronizes with the system clock and updates the DateTimePrinter's
88-
// currentDate property every midnight.
89-
func (d *DateTimePrinter) updateCurrentDateEveryDay() {
71+
// loopUpdateTime updates all time formats, the unix timestamp every second,
72+
// and refreshes the date format if the day has changed.
73+
func (d *DateTimePrinter) loopUpdateTime() {
74+
// Initialize lastDay with a value that forces an update on the first iteration
75+
lastDay := -1
76+
9077
for {
9178
now := d.timeNow()
92-
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)
93-
d.currentDate.Store(now.Format(dateFormat[currentFmt]))
9479

95-
// computing next midnight in local time zone
96-
nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, now.Location())
97-
time.Sleep(time.Until(nextMidnight))
98-
}
99-
}
80+
// 1. Update Time Formats (Always)
81+
for i := 0; i < 3; i++ {
82+
fmt := s.DateTimeFormat(i)
83+
d.cachedTimes[i].Store(now.Format(timeFormat[fmt]))
84+
}
10085

101-
// updateCurrentTimeEverySecond synchronizes with the system clock and updates the DateTimePrinter's
102-
// currentTime property every full second.
103-
func (d *DateTimePrinter) updateCurrentTimeEverySecond() {
104-
for {
105-
now := d.timeNow()
106-
currentFmt := d.currentFormat.Load().(s.DateTimeFormat)
107-
d.currentTime.Store(now.Format(timeFormat[currentFmt]))
86+
// 2. Update Unix Timestamp (Always)
87+
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))
88+
89+
// 3. Update Date Formats (Only if day changed)
90+
if currentDay := now.Day(); currentDay != lastDay {
91+
for i := 0; i < 3; i++ {
92+
fmt := s.DateTimeFormat(i)
93+
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
94+
}
95+
96+
lastDay = currentDay
97+
}
10898

10999
nextSecond := now.Truncate(time.Second).Add(time.Second)
110100
time.Sleep(time.Until(nextSecond))
111101
}
112102
}
113103

114-
// updateCurrentUnixEverySecond synchronizes with the system clock and updates the DateTimePrinter's
115-
// currentTime property every full second.
116-
func (d *DateTimePrinter) updateCurrentUnixEverySecond() {
104+
// loopUpdateDate updates all date formats every 10 mins
105+
func (d *DateTimePrinter) loopUpdateDate() {
117106
for {
118107
now := d.timeNow()
119-
d.currentUnix.Store(strconv.FormatInt(now.Unix(), 10))
120108

121-
nextSecond := now.Truncate(time.Second).Add(time.Second)
122-
time.Sleep(time.Until(nextSecond))
109+
for i := 0; i < 3; i++ {
110+
fmt := s.DateTimeFormat(i)
111+
d.cachedDates[i].Store(now.Format(dateFormat[fmt]))
112+
}
113+
114+
time.Sleep(time.Minute * 10)
123115
}
124116
}
125117

126-
// NewDateTimePrinter initializes DateTimePrinter with the default timeNow function.
127-
func NewDateTimePrinter() *DateTimePrinter {
128-
dateTimePrinter := &DateTimePrinter{timeNow: time.Now}
129-
dateTimePrinter.currentFormat.Store(s.IT)
118+
// GetDateTimePrinter returns the singleton instance.
119+
func GetDateTimePrinter() *DateTimePrinter {
120+
dateTimePrinterOnce.Do(
121+
func() {
122+
dateTimePrinterInstance = &DateTimePrinter{timeNow: time.Now}
123+
dateTimePrinterInstance.init()
124+
},
125+
)
130126

131-
return dateTimePrinter
127+
return dateTimePrinterInstance
132128
}

internal/services/datetime_printer_test.go

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,32 @@ func TestDateTimePrinter_PrintDateTime(t *testing.T) {
1515
return time.Date(2023, time.November, 1, 15, 30, 45, 0, time.UTC)
1616
},
1717
}
18+
dateTimePrinter.init()
1819

1920
t.Run("Return both date and time", func(t *testing.T) {
20-
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
21-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
22-
assert.Empty(t, dateRes)
23-
assert.Empty(t, timeRes)
24-
assert.Equal(t, "01/11/2023 15:30:45", dateTimeRes)
21+
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, true)
22+
assert.Empty(t, unixTs)
23+
assert.NotEmpty(t, dateRes)
24+
assert.NotEmpty(t, timeRes)
25+
assert.Equal(t, "01/11/2023 15:30:45", dateRes+" "+timeRes)
2526
})
2627

2728
t.Run("Return date only", func(t *testing.T) {
28-
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
29-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false)
29+
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, true, false)
3030
assert.Equal(t, "01/11/2023", dateRes)
3131
assert.Equal(t, "", timeRes)
32-
assert.Equal(t, "", dateTimeRes)
32+
assert.Equal(t, "", unixTs)
3333
})
3434

3535
t.Run("Return time only", func(t *testing.T) {
36-
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
37-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true)
36+
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(shared.IT, false, true)
3837
assert.Equal(t, "", dateRes)
3938
assert.Equal(t, "15:30:45", timeRes)
40-
assert.Equal(t, "", dateTimeRes)
39+
assert.Equal(t, "", unixTs)
4140
})
4241

4342
t.Run("Return empty string when both flags are false", func(t *testing.T) {
44-
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
45-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, false)
43+
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.IT, false, false)
4644
assert.Equal(t, "", dateRes)
4745
assert.Equal(t, "", timeRes)
4846
assert.Equal(t, "", dateTimeRes)
@@ -60,6 +58,7 @@ func TestDateTimePrinter_Formats(t *testing.T) {
6058
return fixedFutureTime
6159
},
6260
}
61+
dateTimePrinter.init()
6362

6463
tests := []struct {
6564
name string
@@ -93,18 +92,18 @@ func TestDateTimePrinter_Formats(t *testing.T) {
9392

9493
for _, tt := range tests {
9594
t.Run(tt.name, func(t *testing.T) {
96-
dateTimePrinter.UpdateDateTimeFormat(tt.format)
97-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
98-
assert.Empty(t, dateRes)
99-
assert.Empty(t, timeRes)
100-
assert.Equal(t, tt.wantCombined, dateTimeRes)
95+
dateRes, timeRes, unixTs := dateTimePrinter.RetrieveDateTime(tt.format, true, true)
96+
assert.NotEmpty(t, dateRes)
97+
assert.NotEmpty(t, timeRes)
98+
assert.Empty(t, unixTs)
99+
assert.Equal(t, tt.wantCombined, dateRes+" "+timeRes)
101100

102101
// Also test individual components
103-
d, tRes, _ := dateTimePrinter.RetrieveDateTime(true, false)
102+
d, tRes, _ := dateTimePrinter.RetrieveDateTime(tt.format, true, false)
104103
assert.Equal(t, tt.wantDate, d)
105104
assert.Empty(t, tRes)
106105

107-
d, tRes, _ = dateTimePrinter.RetrieveDateTime(false, true)
106+
d, tRes, _ = dateTimePrinter.RetrieveDateTime(tt.format, false, true)
108107
assert.Empty(t, d)
109108
assert.Equal(t, tt.wantTime, tRes)
110109
})
@@ -121,48 +120,46 @@ func TestDateTimePrinter_UnixTimestamp(t *testing.T) {
121120
return fixedFutureTime
122121
},
123122
}
124-
125-
dateTimePrinter.UpdateDateTimeFormat(shared.UnixTimestamp)
123+
dateTimePrinter.init()
126124

127125
t.Run("Return unix timestamp combined", func(t *testing.T) {
128-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, true)
126+
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, true)
129127
assert.Empty(t, dateRes)
130128
assert.Empty(t, timeRes)
131129
assert.Equal(t, expectedUnix, dateTimeRes)
132130
})
133131

134132
t.Run("Return unix timestamp with date flag only", func(t *testing.T) {
135-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(true, false)
133+
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, true, false)
136134
assert.Empty(t, dateRes)
137135
assert.Empty(t, timeRes)
138136
assert.Equal(t, expectedUnix, dateTimeRes)
139137
})
140138

141139
t.Run("Return unix timestamp with time flag only", func(t *testing.T) {
142-
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(false, true)
140+
dateRes, timeRes, dateTimeRes := dateTimePrinter.RetrieveDateTime(shared.UnixTimestamp, false, true)
143141
assert.Empty(t, dateRes)
144142
assert.Empty(t, timeRes)
145143
assert.Equal(t, expectedUnix, dateTimeRes)
146144
})
147145
}
148146

149147
func TestNewDateTimePrinter(t *testing.T) {
150-
assert.NotNil(t, NewDateTimePrinter())
151-
assert.IsType(t, &DateTimePrinter{}, NewDateTimePrinter())
148+
assert.NotNil(t, GetDateTimePrinter())
149+
assert.IsType(t, &DateTimePrinter{}, GetDateTimePrinter())
152150
}
153151

154152
func TestDateTimePrinter_FullSecondUpdate(t *testing.T) {
155-
dateTimePrinter := NewDateTimePrinter()
153+
dateTimePrinter := GetDateTimePrinter()
156154

157155
t.Run("Return both date and time", func(t *testing.T) {
158-
dateTimePrinter.UpdateDateTimeFormat(shared.IT)
159-
dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(true, true)
160-
assert.Empty(t, dateRes)
161-
assert.Empty(t, timeRes)
156+
dateRes, timeRes, _ := dateTimePrinter.RetrieveDateTime(shared.IT, true, true)
157+
assert.NotEmpty(t, dateRes)
158+
assert.NotEmpty(t, timeRes)
162159

163-
prevTime := dateTimePrinter.currentTime.Load()
160+
prevTime := dateTimePrinter.cachedTimes[shared.IT]
164161
time.Sleep(2 * time.Second)
165-
currTime := dateTimePrinter.currentTime.Load()
162+
currTime := dateTimePrinter.cachedTimes[shared.IT]
166163

167164
assert.NotEqual(t, prevTime, currTime,
168165
"The current %s time should have changed from previous time", currTime, prevTime)

0 commit comments

Comments
 (0)