-
Notifications
You must be signed in to change notification settings - Fork 94
/
memstatlog.go
130 lines (114 loc) · 4.75 KB
/
memstatlog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
* @license
* Copyright 2023 Dynatrace LLC
* 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 memory
import (
"fmt"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/log/field"
"github.com/dynatrace/dynatrace-configuration-as-code/v2/internal/timeutils"
"github.com/spf13/afero"
"runtime"
"time"
)
var memstatFile afero.File
type extendedStats struct {
runtime.MemStats
readableTotal string
readableHeapAlloc string
readableNextGC string
totalAllocMiB float64
heapAllocMiB float64
nextGCAtMiB float64
lastGCTimeUTC time.Time
}
// LogMemStats creates a log line of memory stats which is useful for manually debugging/validating memory consumption.
// This is not used in general, but is highly useful when detailed memory information is needed - in which case it is
// nice to have a reusable method, rather than creating it again.
// Place this method where needed and supply location information - e.g. "before sort" and "after sort".
//
// This method will create a CSV file as well as write into the log.
//
// You can acquire further information - like mem stats sampled by minute or 10sec intervals - by creating a structured
// log and using the utility script tools/parse-memstats-from-json-log.sh to post-process the log file.
func LogMemStats(location string) { // nolint:unused
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
extended := extendedStats{
MemStats: stats,
readableTotal: byteCountToHumanReadableUnit(stats.TotalAlloc),
readableHeapAlloc: byteCountToHumanReadableUnit(stats.HeapAlloc),
readableNextGC: byteCountToHumanReadableUnit(stats.NextGC),
totalAllocMiB: float64(stats.TotalAlloc) / 1024 / 1024,
heapAllocMiB: float64(stats.HeapAlloc) / 1024 / 1024,
nextGCAtMiB: float64(stats.NextGC) / 1024 / 1024,
lastGCTimeUTC: time.Unix(0, int64(stats.LastGC)).UTC(),
}
writeLog(location, extended)
writeCsv(location, extended)
}
func writeLog(location string, stats extendedStats) { // nolint:unused
log.WithFields(
field.F("location", location),
field.F("totalAlloc", stats.readableTotal),
field.F("totalAllocMiB", stats.totalAllocMiB),
field.F("totalAllocB", stats.TotalAlloc),
field.F("heapAlloc", stats.readableHeapAlloc),
field.F("heapAllocMiB", stats.heapAllocMiB),
field.F("heapAllocB", stats.HeapAlloc),
field.F("heapObjects", stats.HeapObjects),
field.F("numGCRuns", stats.NumGC),
field.F("lastGCRunTimestamp", stats.lastGCTimeUTC.String()),
field.F("nextGCRunAt", stats.readableNextGC),
field.F("nextGCRunAtMiB", stats.nextGCAtMiB),
field.F("nextGCRunAtB", stats.NextGC),
field.F("totalGCPauseNs", stats.PauseTotalNs),
).Info("### MEMSTATS ### %s ###\n- totalAlloc: %s\n- heapAlloc: %s\n- heapObjects: %d\n- GC runs: %d\n- Next GC at heap size: %s\n- totalGCPauseTime: %d ns",
location,
stats.readableTotal,
stats.readableHeapAlloc,
stats.HeapObjects,
stats.NumGC,
stats.readableNextGC,
stats.PauseTotalNs)
}
func writeCsv(location string, stats extendedStats) { // nolint:unused
if memstatFile == nil {
createFile("memstatlog.csv")
_, _ = memstatFile.WriteString("heapAlloc, heapAllocMiB, heapAllocByte, heapObjects, lastGCRun, location, nextGCAtHeap, nextGCAtHeapMiB, nextGCAtHeapByte, numGCRuns, totalAlloc, totalAllocMiB, totalAllocByte, totalGCPauseNs, ts\n")
}
//"heapAlloc, heapAllocMB, heapAllocByte, heapObjects, lastGCRun, "location", nextGCAtHeap, nextGCAtHeapMB, nextGCAtHeapByte, numGCRuns, totalAlloc, totalAllocMB, totalAllocByte, totalGCPauseNs, ts\n"
line := fmt.Sprintf("%v, %v, %v, %v, %v, %q, %v, %v, %v, %v, %v, %v, %v, %v, %q\n",
stats.readableHeapAlloc, stats.heapAllocMiB, stats.HeapAlloc,
stats.HeapObjects,
stats.lastGCTimeUTC.String(),
location,
stats.readableNextGC, stats.nextGCAtMiB, stats.NextGC,
stats.NumGC, //numGCRuns
stats.readableTotal, stats.totalAllocMiB, stats.TotalAlloc,
stats.PauseTotalNs, //totalGCPauseNs
time.Now().UTC().String(),
)
_, _ = memstatFile.WriteString(line)
}
func createFile(filename string) { // nolint:unused
fs := afero.NewOsFs()
ts := timeutils.TimeAnchor().Format("20060102-150405")
f, err := fs.Create(ts + "_" + filename)
if err != nil {
panic(err)
}
memstatFile = f
}