/
gmx.go
236 lines (195 loc) · 8.55 KB
/
gmx.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package gmx
import (
"log"
"sync"
"github.com/arinn1204/gmx/pkg/extensions"
"github.com/google/uuid"
)
// Client is the main mbean client.
// This is responsible for creating the JVM, creating individual MBean Clients, and cleaning it all up
// The client is also responsible for orchestrating the JMX operations
type client struct {
maxNumberOfGoRoutines uint // The maximum number of goroutines to be used when doing parallel operations
numberOfConnections uint //The number of active connections in the client
mbeans sync.Map // The map of underlying clients. The map is identified as id -> client
classHandlers sync.Map // The map of type handlers to be used
interfaceHandlers sync.Map
}
type attributeManager struct {
maxNumberOfGoRoutines uint
numberOfConnections *uint //The number of active connections in the client
mbeans *sync.Map
classHandlers *sync.Map // The map of type handlers to be used
interfaceHandlers *sync.Map
}
type operator struct {
maxNumberOfGoRoutines uint
numberOfConnections *uint //The number of active connections in the client
mbeans *sync.Map
classHandlers *sync.Map // The map of type handlers to be used
interfaceHandlers *sync.Map
}
// MBeanClient is an interface that describes the functions needed to fully operate against MBeans over JMXRMI
type MBeanClient interface {
// Initialize will initialize the client:
// This starts the JVM and registers all basic class and interface handlers
// Basic handlers are: Integer, Double, String, Float, Boolean, Long, List, Set, Map<String, Object>
Initialize() error
// Close will close all connections that have been created and then shut down the JVM
Close()
// RegisterClassHandler will register a new class handler
// This is needed if executing operations or retrieving/updating attributes
// that require more complex objects. The default handlers only include primitives and strings
RegisterClassHandler(typeName string, handler extensions.IHandler)
// RegisterInterfaceHandler will register a new interface handler
// In the event no class handler is found for a given type, we will then scan the available
// interfaces implemented by the type. This will then check all the included handlers to determine
// how to convert between go and jni
RegisterInterfaceHandler(typeName string, handler extensions.InterfaceHandler)
// RegisterBean will create a new mbean connection defined by the hostname and port
// The reference to this connection is stored for the life of the operator
RegisterConnection(hostname string, port int) (*uuid.UUID, error)
// This will return the type that is responsible for executing operations
GetOperator() MBeanOperator
// This will return the attribute manager
GetAttributeManager() MBeanAttributeManager
}
// MBeanAttributeManager is a type that will be responsible for managing attributes of a given mbean
type MBeanAttributeManager interface {
// Get will fetch an attribute by a given name for the given bean across all connections
// The args are required in order to be able to deserialize lists from attributes
//
// JavaType and JavaContainerType are only required for Lists/Sets/Maps and any other generic collections
// Value is not used here
Get(domain string, beanName string, attributeName string, args MBeanArgs) (map[uuid.UUID]string, map[uuid.UUID]error)
// Put will change the given attribute across all connections
// The args are required in order to be able to serialize data being sent to the attribute
Put(domain string, beanName string, attributeName string, args MBeanArgs) (map[uuid.UUID]string, map[uuid.UUID]error)
// GetByID will execute Get against the one given ID.
// The args are required in order to be able to deserialize lists from attributes
//
// JavaType and JavaContainerType are only required for Lists/Sets/Maps and any other generic collections
// Value is not used here
GetByID(id uuid.UUID, domain string, beanName string, attributeName string, args MBeanArgs) (string, error)
// PutByID will execute Put against the one given ID.
// The args are required in order to be able to serialize data being sent to the attribute
PutByID(id uuid.UUID, domain string, beanName string, attributeName string, args MBeanArgs) (string, error)
}
// MBeanOperator is a type that is responsible for executing operations against a defined mbean
type MBeanOperator interface {
// ExecuteAgainstID will execute an operation against the given id. This will only target the provided ID
ExecuteAgainstID(id uuid.UUID, domain string, name string, operation string, args ...MBeanArgs) (string, error)
// ExecuteAgainstAll will execute an operation against *all* connected beans.
// These are ran in their own go routines. If there concerns/desired constraints please define MaxNumberOfGoRoutines
ExecuteAgainstAll(domain string, name string, operation string, args ...MBeanArgs) (map[uuid.UUID]string, map[uuid.UUID]error)
}
// MBeanArgs is the container for the operation arguments.
// If you have an MBean defined as the following
//
// getValue(String name)
//
// Then you will want to structure your args like
//
// MBeanArgs{
// Value: "theNameOfTheStringYouAreFetching",
// JavaType: "java.lang.String"
// }
//
// If the intent is to execute a command that accepts a list or a map, then the JavaContainerType will need to be defined
// The JavaType will be the inner type that is being sent, whereas the container type will be the container.
// For example:
//
// MBeanArgs{
// Value: "[\"foo\", \"bar\"]",
// JavaType: "java.lang.String",
// JavaContainerType: "java.util.List"
// }
type MBeanArgs struct {
Value string
JavaType string
JavaContainerType string
}
type batchExecutionResult struct {
id uuid.UUID
result string
err error
}
// CreateClient is a method that will create an unbound MBeanClient
// This means that it will consume as many native threads as there are connected mbeans
func CreateClient() MBeanClient {
client := &client{}
if err := client.Initialize(); err != nil {
log.Fatal(err)
}
return client
}
// CreateClientWithLimitation is a method that will create a bound MBeanClient
// This will only use as many native threads as provided by limit
func CreateClientWithLimitation(limit uint) MBeanClient {
client := &client{
maxNumberOfGoRoutines: limit,
}
if err := client.Initialize(); err != nil {
log.Fatal(err)
}
return client
}
func internalExecuteAgainstAll(numberOfConnections *uint, mbeans *sync.Map, maxNumberOfGoRoutines uint, execution func(uuid.UUID) (string, error)) (map[uuid.UUID]string, map[uuid.UUID]error) {
maxLimitOfConcurrency := *numberOfConnections
results := make(chan batchExecutionResult, maxLimitOfConcurrency)
wg := &sync.WaitGroup{}
if maxNumberOfGoRoutines > 0 {
maxLimitOfConcurrency = maxNumberOfGoRoutines
}
guard := make(chan struct{}, maxLimitOfConcurrency)
mbeans.Range(func(key, value any) bool {
// this will block if there are no available threads
guard <- struct{}{}
wg.Add(1)
go func(id uuid.UUID) {
defer wg.Done()
res, err := execution(id)
result := batchExecutionResult{
id: id,
result: res,
err: err,
}
results <- result
<-guard // this will release this threads guard
}(key.(uuid.UUID))
return true
})
wg.Wait()
result := make(map[uuid.UUID]string)
receivedErrors := make(map[uuid.UUID]error)
for i := 0; i < int(*numberOfConnections); i++ {
res := <-results
result[res.id] = res.result
receivedErrors[res.id] = res.err
}
return result, receivedErrors
}
// GetOperator is a function that returns an mbean operator
// This will be used to execute operations against any given mbean that is
// registered with the client
func (client *client) GetOperator() MBeanOperator {
return &operator{
maxNumberOfGoRoutines: client.maxNumberOfGoRoutines,
mbeans: &client.mbeans,
classHandlers: &client.classHandlers,
interfaceHandlers: &client.interfaceHandlers,
numberOfConnections: &client.numberOfConnections,
}
}
// GetAttributeManager is a function that returns an mbean attribute manager
// This will be used to read and update attributes for any/all mbeans that are associated
// with the client that creates the manager
func (client *client) GetAttributeManager() MBeanAttributeManager {
return &attributeManager{
maxNumberOfGoRoutines: client.maxNumberOfGoRoutines,
mbeans: &client.mbeans,
classHandlers: &client.classHandlers,
interfaceHandlers: &client.interfaceHandlers,
numberOfConnections: &client.numberOfConnections,
}
}