go-duck-cli 1.4.6 → 1.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/generators/postman.js +5 -3
- package/generators/swagger.js +4 -2
- package/generators/telemetry.js +136 -9
- package/go.mod +15 -0
- package/go.sum +20 -0
- package/index.js +13 -4
- package/package.json +1 -1
- package/parser/gdl.js +9 -5
- package/templates/docs/pages/gdl-entities.hbs +53 -0
- package/templates/go/entity.go.hbs +3 -0
- package/templates/go/router.go.hbs +196 -13
- package/test-go/devops/k8s/otel-collector.yml +23 -0
- package/test-go/go.mod +64 -0
- package/test-go/go.sum +154 -0
- package/test-go/internal/telemetry/metrics_collector.go +88 -0
- package/test-go/internal/telemetry/otel.go +69 -0
- package/test-go/internal/telemetry/system_metrics.go +198 -0
- package/test_datatypes.go +19 -0
- package/test_time.go +22 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
|
|
2
|
+
package telemetry
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"context"
|
|
6
|
+
"fmt"
|
|
7
|
+
"log"
|
|
8
|
+
|
|
9
|
+
"undefined/config"
|
|
10
|
+
"go.opentelemetry.io/otel"
|
|
11
|
+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
12
|
+
"go.opentelemetry.io/otel/propagation"
|
|
13
|
+
"go.opentelemetry.io/otel/sdk/resource"
|
|
14
|
+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
15
|
+
semconv "go.opentelemetry.io/otel/semconv/v1.12.0"
|
|
16
|
+
"google.golang.org/grpc"
|
|
17
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
// InitTelemetry initializes OpenTelemetry SDK
|
|
21
|
+
func InitTelemetry(cfg *config.Config) (func(context.Context) error, error) {
|
|
22
|
+
if !cfg.GoDuck.Telemetry.OTel.Enabled {
|
|
23
|
+
log.Println("OpenTelemetry is disabled.")
|
|
24
|
+
return func(context.Context) error { return nil }, nil
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ctx := context.Background()
|
|
28
|
+
|
|
29
|
+
// 1. Setup Resource
|
|
30
|
+
res, err := resource.New(ctx,
|
|
31
|
+
resource.WithAttributes(
|
|
32
|
+
semconv.ServiceNameKey.String(cfg.GoDuck.Name),
|
|
33
|
+
semconv.ServiceVersionKey.String(cfg.GoDuck.Version),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
if err != nil {
|
|
37
|
+
return nil, fmt.Errorf("failed to create resource: %w", err)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Setup OTLP Exporter (gRPC)
|
|
41
|
+
conn, err := grpc.DialContext(ctx, cfg.GoDuck.Telemetry.OTel.Endpoint,
|
|
42
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
43
|
+
grpc.WithBlock(),
|
|
44
|
+
)
|
|
45
|
+
if err != nil {
|
|
46
|
+
return nil, fmt.Errorf("failed to create gRPC connection to OTel collector: %w", err)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
traceExporter, err := otlptracegrpc.New(ctx, otlptracegrpc.WithGRPCConn(conn))
|
|
50
|
+
if err != nil {
|
|
51
|
+
return nil, fmt.Errorf("failed to create trace exporter: %w", err)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Setup Tracer Provider
|
|
55
|
+
bsp := sdktrace.NewBatchSpanProcessor(traceExporter)
|
|
56
|
+
tp := sdktrace.NewTracerProvider(
|
|
57
|
+
sdktrace.WithSampler(sdktrace.TraceIDRatioBased(cfg.GoDuck.Telemetry.OTel.SamplerRatio)),
|
|
58
|
+
sdktrace.WithResource(res),
|
|
59
|
+
sdktrace.WithSpanProcessor(bsp),
|
|
60
|
+
)
|
|
61
|
+
otel.SetTracerProvider(tp)
|
|
62
|
+
|
|
63
|
+
// 4. Setup Text Map Propagator
|
|
64
|
+
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
|
|
65
|
+
|
|
66
|
+
log.Printf("OpenTelemetry initialized with endpoint: %s", cfg.GoDuck.Telemetry.OTel.Endpoint)
|
|
67
|
+
|
|
68
|
+
return tp.Shutdown, nil
|
|
69
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
|
|
2
|
+
package telemetry
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"runtime"
|
|
6
|
+
"time"
|
|
7
|
+
"github.com/shirou/gopsutil/v3/cpu"
|
|
8
|
+
"github.com/shirou/gopsutil/v3/process"
|
|
9
|
+
"github.com/shirou/gopsutil/v3/mem"
|
|
10
|
+
"os"
|
|
11
|
+
"fmt"
|
|
12
|
+
"strconv"
|
|
13
|
+
"strings"
|
|
14
|
+
"sync"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
var (
|
|
18
|
+
cgroupCPULock sync.Mutex
|
|
19
|
+
lastCgroupTime time.Time
|
|
20
|
+
lastCgroupUsageNs uint64
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
type SystemMetrics struct {
|
|
24
|
+
Uptime string `json:"uptime"`
|
|
25
|
+
StartTime string `json:"start_time"`
|
|
26
|
+
ProcessCPUUsage float64 `json:"process_cpu_usage"`
|
|
27
|
+
SystemCPUUsage float64 `json:"system_cpu_usage"`
|
|
28
|
+
PodCPULimitPct float64 `json:"pod_cpu_limit_pct"`
|
|
29
|
+
IsPodCPULimited bool `json:"is_pod_cpu_limited"`
|
|
30
|
+
CPUThreadsUsage []float64 `json:"cpu_threads_usage"`
|
|
31
|
+
SystemCPUCount int `json:"system_cpu_count"`
|
|
32
|
+
ProcessFilesOpen int32 `json:"process_files_open"`
|
|
33
|
+
|
|
34
|
+
TotalMemMB uint64 `json:"total_mem_mb"`
|
|
35
|
+
HeapAllocMB uint64 `json:"heap_alloc_mb"`
|
|
36
|
+
HeapSysMB uint64 `json:"heap_sys_mb"`
|
|
37
|
+
NumGC uint32 `json:"num_gc"`
|
|
38
|
+
PauseTotalMs uint64 `json:"gc_pause_total_ms"`
|
|
39
|
+
Goroutines int `json:"goroutines"`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func getContainerMemoryLimit() uint64 {
|
|
43
|
+
// Check cgroup v2
|
|
44
|
+
if data, err := os.ReadFile("/sys/fs/cgroup/memory.max"); err == nil {
|
|
45
|
+
val := strings.TrimSpace(string(data))
|
|
46
|
+
if val != "max" && val != "" {
|
|
47
|
+
if limit, err := strconv.ParseUint(val, 10, 64); err == nil {
|
|
48
|
+
return limit
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Check cgroup v1
|
|
53
|
+
if data, err := os.ReadFile("/sys/fs/cgroup/memory/memory.limit_in_bytes"); err == nil {
|
|
54
|
+
val := strings.TrimSpace(string(data))
|
|
55
|
+
if limit, err := strconv.ParseUint(val, 10, 64); err == nil {
|
|
56
|
+
// Some systems report an absurdly high number (e.g. 9223372036854771712) for "no limit"
|
|
57
|
+
if limit < 9000000000000000000 {
|
|
58
|
+
return limit
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func getCgroupCPUUsageNs() (uint64, error) {
|
|
66
|
+
// try v2
|
|
67
|
+
if data, err := os.ReadFile("/sys/fs/cgroup/cpu.stat"); err == nil {
|
|
68
|
+
lines := strings.Split(string(data), "
|
|
69
|
+
")
|
|
70
|
+
for _, line := range lines {
|
|
71
|
+
if strings.HasPrefix(line, "usage_usec") {
|
|
72
|
+
parts := strings.Fields(line)
|
|
73
|
+
if len(parts) == 2 {
|
|
74
|
+
if usec, err := strconv.ParseUint(parts[1], 10, 64); err == nil {
|
|
75
|
+
return usec * 1000, nil // to nanoseconds
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// try v1
|
|
82
|
+
if data, err := os.ReadFile("/sys/fs/cgroup/cpuacct/cpuacct.usage"); err == nil {
|
|
83
|
+
val := strings.TrimSpace(string(data))
|
|
84
|
+
if ns, err := strconv.ParseUint(val, 10, 64); err == nil {
|
|
85
|
+
return ns, nil
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return 0, fmt.Errorf("not found")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func getCgroupCPULimitCores() float64 {
|
|
92
|
+
// check v2
|
|
93
|
+
if data, err := os.ReadFile("/sys/fs/cgroup/cpu.max"); err == nil {
|
|
94
|
+
parts := strings.Fields(strings.TrimSpace(string(data)))
|
|
95
|
+
if len(parts) == 2 && parts[0] != "max" {
|
|
96
|
+
max, err1 := strconv.ParseFloat(parts[0], 64)
|
|
97
|
+
period, err2 := strconv.ParseFloat(parts[1], 64)
|
|
98
|
+
if err1 == nil && err2 == nil && period > 0 {
|
|
99
|
+
return max / period
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// check v1
|
|
104
|
+
if quotaData, err := os.ReadFile("/sys/fs/cgroup/cpu/cpu.cfs_quota_us"); err == nil {
|
|
105
|
+
quotaStr := strings.TrimSpace(string(quotaData))
|
|
106
|
+
if quotaStr != "-1" {
|
|
107
|
+
if quota, err := strconv.ParseFloat(quotaStr, 64); err == nil {
|
|
108
|
+
if periodData, err := os.ReadFile("/sys/fs/cgroup/cpu/cpu.cfs_period_us"); err == nil {
|
|
109
|
+
if period, err := strconv.ParseFloat(strings.TrimSpace(string(periodData)), 64); err == nil && period > 0 {
|
|
110
|
+
return quota / period
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return 0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func CollectSystemMetrics() SystemMetrics {
|
|
120
|
+
var m runtime.MemStats
|
|
121
|
+
runtime.ReadMemStats(&m)
|
|
122
|
+
|
|
123
|
+
cpuPercent, _ := cpu.Percent(0, false)
|
|
124
|
+
threadsPercent, _ := cpu.Percent(0, true)
|
|
125
|
+
sysCPU := 0.0
|
|
126
|
+
if len(cpuPercent) > 0 {
|
|
127
|
+
sysCPU = cpuPercent[0]
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
p, err := process.NewProcess(int32(os.Getpid()))
|
|
131
|
+
procCPU := 0.0
|
|
132
|
+
var openFiles int32 = 0
|
|
133
|
+
if err == nil {
|
|
134
|
+
procCPU, _ = p.CPUPercent()
|
|
135
|
+
files, _ := p.OpenFiles()
|
|
136
|
+
openFiles = int32(len(files))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
vMem, _ := mem.VirtualMemory()
|
|
140
|
+
totalMemMB := uint64(0)
|
|
141
|
+
|
|
142
|
+
// 1. Try Kubernetes / Docker Cgroup Limits first
|
|
143
|
+
cgroupLimitBytes := getContainerMemoryLimit()
|
|
144
|
+
if cgroupLimitBytes > 0 {
|
|
145
|
+
totalMemMB = cgroupLimitBytes / 1024 / 1024
|
|
146
|
+
} else if vMem != nil {
|
|
147
|
+
// 2. Fallback to underlying physical host memory
|
|
148
|
+
totalMemMB = vMem.Total / 1024 / 1024
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
appMetrics := GetGlobalMetrics()
|
|
152
|
+
uptime := time.Since(appMetrics.StartTime)
|
|
153
|
+
|
|
154
|
+
uptimeStr := fmt.Sprintf("%d days, %d hours, %d minutes",
|
|
155
|
+
int(uptime.Hours())/24, int(uptime.Hours())%24, int(uptime.Minutes())%60)
|
|
156
|
+
|
|
157
|
+
podCPULimitPct := 0.0
|
|
158
|
+
isPodCPULimited := false
|
|
159
|
+
|
|
160
|
+
limitCores := getCgroupCPULimitCores()
|
|
161
|
+
if limitCores > 0 {
|
|
162
|
+
isPodCPULimited = true
|
|
163
|
+
cgroupCPULock.Lock()
|
|
164
|
+
usageNs, err := getCgroupCPUUsageNs()
|
|
165
|
+
now := time.Now()
|
|
166
|
+
if err == nil {
|
|
167
|
+
if !lastCgroupTime.IsZero() && usageNs > lastCgroupUsageNs {
|
|
168
|
+
timeDelta := float64(now.Sub(lastCgroupTime).Nanoseconds())
|
|
169
|
+
usageDelta := float64(usageNs - lastCgroupUsageNs)
|
|
170
|
+
if timeDelta > 0 {
|
|
171
|
+
coresUsed := usageDelta / timeDelta
|
|
172
|
+
podCPULimitPct = (coresUsed / limitCores) * 100.0
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
lastCgroupUsageNs = usageNs
|
|
176
|
+
lastCgroupTime = now
|
|
177
|
+
}
|
|
178
|
+
cgroupCPULock.Unlock()
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return SystemMetrics{
|
|
182
|
+
Uptime: uptimeStr,
|
|
183
|
+
StartTime: appMetrics.StartTime.Format(time.RFC1123),
|
|
184
|
+
ProcessCPUUsage: procCPU,
|
|
185
|
+
SystemCPUUsage: sysCPU,
|
|
186
|
+
PodCPULimitPct: podCPULimitPct,
|
|
187
|
+
IsPodCPULimited: isPodCPULimited,
|
|
188
|
+
CPUThreadsUsage: threadsPercent,
|
|
189
|
+
SystemCPUCount: runtime.NumCPU(),
|
|
190
|
+
ProcessFilesOpen: openFiles,
|
|
191
|
+
TotalMemMB: totalMemMB,
|
|
192
|
+
HeapAllocMB: m.HeapAlloc / 1024 / 1024,
|
|
193
|
+
HeapSysMB: m.HeapSys / 1024 / 1024,
|
|
194
|
+
NumGC: m.NumGC,
|
|
195
|
+
PauseTotalMs: m.PauseTotalNs / 1000000,
|
|
196
|
+
Goroutines: runtime.NumGoroutine(),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"gorm.io/datatypes"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type TestStruct struct {
|
|
10
|
+
D datatypes.Date `json:"d"`
|
|
11
|
+
T datatypes.Time `json:"t"`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
func main() {
|
|
15
|
+
j := `{"d": "2024-01-01", "t": "12:00:00"}`
|
|
16
|
+
var ts TestStruct
|
|
17
|
+
fmt.Println("Err:", json.Unmarshal([]byte(j), &ts))
|
|
18
|
+
fmt.Println("Result:", ts)
|
|
19
|
+
}
|
package/test_time.go
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"time"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
type TestStruct struct {
|
|
10
|
+
T time.Time `json:"t"`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
func main() {
|
|
14
|
+
j1 := `{"t": "2024-01-01"}`
|
|
15
|
+
j2 := `{"t": "12:00:00"}`
|
|
16
|
+
j3 := `{"t": "2024-01-01T12:00:00Z"}`
|
|
17
|
+
|
|
18
|
+
var t1, t2, t3 TestStruct
|
|
19
|
+
fmt.Println("Date:", json.Unmarshal([]byte(j1), &t1))
|
|
20
|
+
fmt.Println("Time:", json.Unmarshal([]byte(j2), &t2))
|
|
21
|
+
fmt.Println("Instant:", json.Unmarshal([]byte(j3), &t3))
|
|
22
|
+
}
|