Skip to content

Commit e4e2391

Browse files
authored
Report memory usage by application (#7966) (#7967)
Changes the license reporting to not only show the total managed memory but also show a breakdown by application. (cherry picked from commit 704f386)
1 parent 8f067b8 commit e4e2391

File tree

8 files changed

+282
-83
lines changed

8 files changed

+282
-83
lines changed

docs/operating-eck/licensing.asciidoc

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,23 @@ The operator periodically writes the total amount of Elastic resources under man
105105
----
106106
> kubectl -n elastic-system get configmap elastic-licensing -o json | jq .data
107107
{
108+
"apm_memory": "0.50GiB",
109+
"apm_memory_bytes": "536870912",
110+
"eck_license_expiry_date": "2025-01-01T00:59:59+01:00",
108111
"eck_license_level": "enterprise",
109-
"eck_license_expiry_date": "2022-01-01T00:59:59+01:00",
112+
"elasticsearch_memory": "18.00GiB",
113+
"elasticsearch_memory_bytes": "19327352832",
110114
"enterprise_resource_units": "1",
111-
"max_enterprise_resource_units": "10",
112-
"timestamp": "2020-01-03T23:38:20Z",
113-
"total_managed_memory": "64GiB",
114-
"total_managed_memory_bytes": "68719476736"
115+
"enterprise_search_memory": "4.00GiB",
116+
"enterprise_search_memory_bytes": "4294967296",
117+
"kibana_memory": "1.00GiB",
118+
"kibana_memory_bytes": "1073741824",
119+
"logstash_memory": "2.00GiB",
120+
"logstash_memory_bytes": "2147483648",
121+
"max_enterprise_resource_units": "250",
122+
"timestamp": "2024-07-26T12:40:42+02:00",
123+
"total_managed_memory": "25.50GiB",
124+
"total_managed_memory_bytes": "27380416512"
115125
}
116126
----
117127

@@ -120,12 +130,30 @@ If the operator metrics endpoint is enabled with the `--metrics-port` flag (chec
120130
[source,shell]
121131
----
122132
> curl "$ECK_METRICS_ENDPOINT" | grep elastic_licensing
133+
# HELP elastic_licensing_enterprise_resource_units_max Maximum number of enterprise resource units available
134+
# TYPE elastic_licensing_enterprise_resource_units_max gauge
135+
elastic_licensing_enterprise_resource_units_max{license_level="enterprise"} 250
123136
# HELP elastic_licensing_enterprise_resource_units_total Total enterprise resource units used
124137
# TYPE elastic_licensing_enterprise_resource_units_total gauge
125-
elastic_licensing_enterprise_resource_units_total{license_level="basic"} 6
126-
# HELP elastic_licensing_memory_gigabytes_total Total memory used in GB
127-
# TYPE elastic_licensing_memory_gigabytes_total gauge
128-
elastic_licensing_memory_gigabytes_total{license_level="basic"} 357.01915648
138+
elastic_licensing_enterprise_resource_units_total{license_level="enterprise"} 1
139+
# HELP elastic_licensing_memory_gibibytes_apm Memory used by APM server in GiB
140+
# TYPE elastic_licensing_memory_gibibytes_apm gauge
141+
elastic_licensing_memory_gibibytes_apm{license_level="enterprise"} 0.5
142+
# HELP elastic_licensing_memory_gibibytes_elasticsearch Memory used by Elasticsearch in GiB
143+
# TYPE elastic_licensing_memory_gibibytes_elasticsearch gauge
144+
elastic_licensing_memory_gibibytes_elasticsearch{license_level="enterprise"} 18
145+
# HELP elastic_licensing_memory_gibibytes_enterprise_search Memory used by Enterprise Search in GiB
146+
# TYPE elastic_licensing_memory_gibibytes_enterprise_search gauge
147+
elastic_licensing_memory_gibibytes_enterprise_search{license_level="enterprise"} 4
148+
# HELP elastic_licensing_memory_gibibytes_kibana Memory used by Kibana in GiB
149+
# TYPE elastic_licensing_memory_gibibytes_kibana gauge
150+
elastic_licensing_memory_gibibytes_kibana{license_level="enterprise"} 1
151+
# HELP elastic_licensing_memory_gibibytes_logstash Memory used by Logstash in GiB
152+
# TYPE elastic_licensing_memory_gibibytes_logstash gauge
153+
elastic_licensing_memory_gibibytes_logstash{license_level="enterprise"} 2
154+
# HELP elastic_licensing_memory_gibibytes_total Total memory used in GiB
155+
# TYPE elastic_licensing_memory_gibibytes_total gauge
156+
elastic_licensing_memory_gibibytes_total{license_level="enterprise"} 25.5
129157
----
130158

131159
NOTE: Logstash resources managed by ECK will be counted towards ERU usage for informational purposes. Billable consumption depends on license terms on a per customer basis (See link:https://www.elastic.co/agreements/global/self-managed[Self Managed Subscription Agreement])

pkg/license/aggregator.go

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ import (
3030
ulog "github.com/elastic/cloud-on-k8s/v2/pkg/utils/log"
3131
)
3232

33-
// Aggregator aggregates the total of resources of all Elastic managed components
34-
type Aggregator struct {
33+
// aggregator aggregates the total of resources of all Elastic managed components
34+
type aggregator struct {
3535
client k8s.Client
3636
}
3737

38-
type aggregate func(ctx context.Context) (resource.Quantity, error)
38+
type aggregate func(ctx context.Context) (managedMemory, error)
3939

40-
// AggregateMemory aggregates the total memory of all Elastic managed components
41-
func (a Aggregator) AggregateMemory(ctx context.Context) (resource.Quantity, error) {
42-
var totalMemory resource.Quantity
40+
// aggregateMemory aggregates the total memory of all Elastic managed components
41+
func (a aggregator) aggregateMemory(ctx context.Context) (memoryUsage, error) {
42+
usage := newMemoryUsage()
4343

4444
for _, f := range []aggregate{
4545
a.aggregateElasticsearchMemory,
@@ -50,19 +50,19 @@ func (a Aggregator) AggregateMemory(ctx context.Context) (resource.Quantity, err
5050
} {
5151
memory, err := f(ctx)
5252
if err != nil {
53-
return resource.Quantity{}, err
53+
return memoryUsage{}, err
5454
}
55-
totalMemory.Add(memory)
55+
usage.add(memory)
5656
}
5757

58-
return totalMemory, nil
58+
return usage, nil
5959
}
6060

61-
func (a Aggregator) aggregateElasticsearchMemory(ctx context.Context) (resource.Quantity, error) {
61+
func (a aggregator) aggregateElasticsearchMemory(ctx context.Context) (managedMemory, error) {
6262
var esList esv1.ElasticsearchList
6363
err := a.client.List(context.Background(), &esList)
6464
if err != nil {
65-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Elasticsearch memory")
65+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Elasticsearch memory")
6666
}
6767

6868
var total resource.Quantity
@@ -75,7 +75,7 @@ func (a Aggregator) aggregateElasticsearchMemory(ctx context.Context) (resource.
7575
nodespec.DefaultMemoryLimits,
7676
)
7777
if err != nil {
78-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Elasticsearch memory")
78+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Elasticsearch memory")
7979
}
8080

8181
total.Add(multiply(mem, nodeSet.Count))
@@ -84,14 +84,14 @@ func (a Aggregator) aggregateElasticsearchMemory(ctx context.Context) (resource.
8484
}
8585
}
8686

87-
return total, nil
87+
return managedMemory{total, elasticsearchKey}, nil
8888
}
8989

90-
func (a Aggregator) aggregateEnterpriseSearchMemory(ctx context.Context) (resource.Quantity, error) {
90+
func (a aggregator) aggregateEnterpriseSearchMemory(ctx context.Context) (managedMemory, error) {
9191
var entList entv1.EnterpriseSearchList
9292
err := a.client.List(context.Background(), &entList)
9393
if err != nil {
94-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Enterprise Search memory")
94+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Enterprise Search memory")
9595
}
9696

9797
var total resource.Quantity
@@ -103,22 +103,22 @@ func (a Aggregator) aggregateEnterpriseSearchMemory(ctx context.Context) (resour
103103
enterprisesearch.DefaultMemoryLimits,
104104
)
105105
if err != nil {
106-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Enterprise Search memory")
106+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Enterprise Search memory")
107107
}
108108

109109
total.Add(multiply(mem, ent.Spec.Count))
110110
ulog.FromContext(ctx).V(1).Info("Collecting", "namespace", ent.Namespace, "ent_name", ent.Name,
111111
"memory", mem.String(), "count", ent.Spec.Count)
112112
}
113113

114-
return total, nil
114+
return managedMemory{total, entSearchKey}, nil
115115
}
116116

117-
func (a Aggregator) aggregateKibanaMemory(ctx context.Context) (resource.Quantity, error) {
117+
func (a aggregator) aggregateKibanaMemory(ctx context.Context) (managedMemory, error) {
118118
var kbList kbv1.KibanaList
119119
err := a.client.List(context.Background(), &kbList)
120120
if err != nil {
121-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Kibana memory")
121+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Kibana memory")
122122
}
123123

124124
var total resource.Quantity
@@ -130,22 +130,22 @@ func (a Aggregator) aggregateKibanaMemory(ctx context.Context) (resource.Quantit
130130
kibana.DefaultMemoryLimits,
131131
)
132132
if err != nil {
133-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Kibana memory")
133+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Kibana memory")
134134
}
135135

136136
total.Add(multiply(mem, kb.Spec.Count))
137137
ulog.FromContext(ctx).V(1).Info("Collecting", "namespace", kb.Namespace, "kibana_name", kb.Name,
138138
"memory", mem.String(), "count", kb.Spec.Count)
139139
}
140140

141-
return total, nil
141+
return managedMemory{total, kibanaKey}, nil
142142
}
143143

144-
func (a Aggregator) aggregateLogstashMemory(ctx context.Context) (resource.Quantity, error) {
144+
func (a aggregator) aggregateLogstashMemory(ctx context.Context) (managedMemory, error) {
145145
var lsList lsv1alpha1.LogstashList
146146
err := a.client.List(context.Background(), &lsList)
147147
if err != nil {
148-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Logstash memory")
148+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Logstash memory")
149149
}
150150

151151
var total resource.Quantity
@@ -157,22 +157,22 @@ func (a Aggregator) aggregateLogstashMemory(ctx context.Context) (resource.Quant
157157
logstash.DefaultMemoryLimit,
158158
)
159159
if err != nil {
160-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate Logstash memory")
160+
return managedMemory{}, errors.Wrap(err, "failed to aggregate Logstash memory")
161161
}
162162

163163
total.Add(multiply(mem, ls.Spec.Count))
164164
ulog.FromContext(ctx).V(1).Info("Collecting", "namespace", ls.Namespace, "logstash_name", ls.Name,
165165
"memory", mem.String(), "count", ls.Spec.Count)
166166
}
167167

168-
return total, nil
168+
return managedMemory{total, logstashKey}, nil
169169
}
170170

171-
func (a Aggregator) aggregateApmServerMemory(ctx context.Context) (resource.Quantity, error) {
171+
func (a aggregator) aggregateApmServerMemory(ctx context.Context) (managedMemory, error) {
172172
var asList apmv1.ApmServerList
173173
err := a.client.List(context.Background(), &asList)
174174
if err != nil {
175-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate APM Server memory")
175+
return managedMemory{}, errors.Wrap(err, "failed to aggregate APM Server memory")
176176
}
177177

178178
var total resource.Quantity
@@ -184,15 +184,15 @@ func (a Aggregator) aggregateApmServerMemory(ctx context.Context) (resource.Quan
184184
apmserver.DefaultMemoryLimits,
185185
)
186186
if err != nil {
187-
return resource.Quantity{}, errors.Wrap(err, "failed to aggregate APM Server memory")
187+
return managedMemory{}, errors.Wrap(err, "failed to aggregate APM Server memory")
188188
}
189189

190190
total.Add(multiply(mem, as.Spec.Count))
191191
ulog.FromContext(ctx).V(1).Info("Collecting", "namespace", as.Namespace, "as_name", as.Name,
192192
"memory", mem.String(), "count", as.Spec.Count)
193193
}
194194

195-
return total, nil
195+
return managedMemory{total, apmKey}, nil
196196
}
197197

198198
// containerMemLimits reads the container memory limits from the resource specification with fallback

pkg/license/aggregator_test.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,20 @@ func TestMemFromNodeOpts(t *testing.T) {
138138
func TestAggregator(t *testing.T) {
139139
objects := readObjects(t, "testdata/stack.yaml")
140140
client := k8s.NewFakeClient(objects...)
141-
aggregator := Aggregator{client: client}
141+
aggregator := aggregator{client: client}
142142

143-
val, err := aggregator.AggregateMemory(context.Background())
143+
val, err := aggregator.aggregateMemory(context.Background())
144144
require.NoError(t, err)
145-
require.Equal(t, 329.9073486328125, inGiB(val))
145+
for k, v := range map[string]float64{
146+
elasticsearchKey: 294.0,
147+
kibanaKey: 5.9073486328125,
148+
apmKey: 2.0,
149+
entSearchKey: 24.0,
150+
logstashKey: 4.0,
151+
} {
152+
require.Equal(t, v, val.appUsage[k].inGiB(), k)
153+
}
154+
require.Equal(t, 329.9073486328125, val.totalMemory.inGiB(), "total")
146155
}
147156

148157
func readObjects(t *testing.T, filePath string) []client.Object {

pkg/license/license.go

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,75 @@ const (
3737
Type = "elastic-usage"
3838
// GiB represents the number of bytes for 1 GiB
3939
GiB = 1024 * 1024 * 1024
40+
41+
elasticsearchKey = "elasticsearch"
42+
kibanaKey = "kibana"
43+
apmKey = "apm"
44+
entSearchKey = "enterprise_search"
45+
logstashKey = "logstash"
46+
totalKey = "total_managed"
4047
)
4148

49+
type managedMemory struct {
50+
resource.Quantity
51+
label string
52+
}
53+
54+
func newManagedMemory(binarySI int64, label string) managedMemory {
55+
return managedMemory{
56+
Quantity: *resource.NewQuantity(binarySI, resource.BinarySI),
57+
label: label,
58+
}
59+
}
60+
61+
func (mm managedMemory) inGiB() float64 {
62+
return inGiB(mm.Quantity)
63+
}
64+
65+
func (mm managedMemory) intoMap(m map[string]string) {
66+
m[mm.label+"_memory"] = fmt.Sprintf("%0.2fGiB", inGiB(mm.Quantity))
67+
m[mm.label+"_memory_bytes"] = fmt.Sprintf("%d", mm.Quantity.Value())
68+
}
69+
70+
type memoryUsage struct {
71+
appUsage map[string]managedMemory
72+
totalMemory managedMemory
73+
}
74+
75+
func newMemoryUsage() memoryUsage {
76+
return memoryUsage{
77+
appUsage: map[string]managedMemory{},
78+
totalMemory: managedMemory{label: totalKey},
79+
}
80+
}
81+
82+
func (mu *memoryUsage) add(memory managedMemory) {
83+
mu.appUsage[memory.label] = memory
84+
mu.totalMemory.Add(memory.Quantity)
85+
}
86+
4287
// LicensingInfo represents information about the operator license including the total memory of all Elastic managed
4388
// components
4489
type LicensingInfo struct {
90+
memoryUsage
4591
Timestamp string
4692
EckLicenseLevel string
4793
EckLicenseExpiryDate *time.Time
48-
TotalManagedMemoryGiB float64
49-
TotalManagedMemoryBytes int64
5094
MaxEnterpriseResourceUnits int64
5195
EnterpriseResourceUnits int64
5296
}
5397

5498
// toMap transforms a LicensingInfo to a map of string, in order to fill in the data of a config map
5599
func (li LicensingInfo) toMap() map[string]string {
56100
m := map[string]string{
57-
"timestamp": li.Timestamp,
58-
"eck_license_level": li.EckLicenseLevel,
59-
"total_managed_memory": fmt.Sprintf("%0.2fGiB", li.TotalManagedMemoryGiB),
60-
"total_managed_memory_bytes": fmt.Sprintf("%d", li.TotalManagedMemoryBytes),
61-
"enterprise_resource_units": strconv.FormatInt(li.EnterpriseResourceUnits, 10),
101+
"timestamp": li.Timestamp,
102+
"eck_license_level": li.EckLicenseLevel,
103+
"enterprise_resource_units": strconv.FormatInt(li.EnterpriseResourceUnits, 10),
104+
}
105+
for _, v := range li.appUsage {
106+
v.intoMap(m)
62107
}
108+
li.totalMemory.intoMap(m)
63109

64110
if li.MaxEnterpriseResourceUnits > 0 {
65111
m["max_enterprise_resource_units"] = strconv.FormatInt(li.MaxEnterpriseResourceUnits, 10)
@@ -74,7 +120,12 @@ func (li LicensingInfo) toMap() map[string]string {
74120

75121
func (li LicensingInfo) ReportAsMetrics() {
76122
labels := prometheus.Labels{metrics.LicenseLevelLabel: li.EckLicenseLevel}
77-
metrics.LicensingTotalMemoryGauge.With(labels).Set(li.TotalManagedMemoryGiB)
123+
metrics.LicensingTotalMemoryGauge.With(labels).Set(li.totalMemory.inGiB())
124+
metrics.LicensingESMemoryGauge.With(labels).Set(li.appUsage[elasticsearchKey].inGiB())
125+
metrics.LicensingKBMemoryGauge.With(labels).Set(li.appUsage[kibanaKey].inGiB())
126+
metrics.LicensingAPMMemoryGauge.With(labels).Set(li.appUsage[apmKey].inGiB())
127+
metrics.LicensingEntSearchMemoryGauge.With(labels).Set(li.appUsage[entSearchKey].inGiB())
128+
metrics.LicensingLogstashMemoryGauge.With(labels).Set(li.appUsage[logstashKey].inGiB())
78129
metrics.LicensingTotalERUGauge.With(labels).Set(float64(li.EnterpriseResourceUnits))
79130

80131
if li.MaxEnterpriseResourceUnits > 0 {
@@ -89,19 +140,18 @@ type LicensingResolver struct {
89140
}
90141

91142
// ToInfo returns licensing information given the total memory of all Elastic managed components
92-
func (r LicensingResolver) ToInfo(ctx context.Context, totalMemory resource.Quantity) (LicensingInfo, error) {
143+
func (r LicensingResolver) ToInfo(ctx context.Context, memoryUsage memoryUsage) (LicensingInfo, error) {
93144
operatorLicense, err := r.getOperatorLicense(ctx)
94145
if err != nil {
95146
return LicensingInfo{}, err
96147
}
97148

98149
licensingInfo := LicensingInfo{
150+
memoryUsage: memoryUsage,
99151
Timestamp: time.Now().Format(time.RFC3339),
100152
EckLicenseLevel: r.getOperatorLicenseLevel(operatorLicense),
101153
EckLicenseExpiryDate: r.getOperatorLicenseExpiry(operatorLicense),
102-
TotalManagedMemoryGiB: inGiB(totalMemory),
103-
TotalManagedMemoryBytes: totalMemory.Value(),
104-
EnterpriseResourceUnits: inEnterpriseResourceUnits(totalMemory),
154+
EnterpriseResourceUnits: inEnterpriseResourceUnits(memoryUsage.totalMemory.Quantity),
105155
}
106156

107157
// include the max ERUs only for a non trial/basic license

0 commit comments

Comments
 (0)