Skip to content

Commit 451681e

Browse files
authored
feat: support for other event types (#2)
* Add .editorconfig * Add TODO * Refactor to support multiple event types * Include Java specific .gitignore entries * Narrow indent spec to subset of files * Use enums for units and agg. type * Move HTTP API enums to own namespace * Specify units and agg. type per event type * Fix some bugs * Set units for ALLOC to BYTES * Fix NPE; introduce timeseries name * Use Units.OBJECTS for EventType.ALLOC
1 parent a4dc654 commit 451681e

File tree

11 files changed

+190
-28
lines changed

11 files changed

+190
-28
lines changed

.editorconfig

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
root = true
2+
3+
[*]
4+
end_of_line = lf
5+
indent_style = space
6+
7+
[{*.java,build.gradle,settings.gradle}]
8+
indent_size = 4

.gitignore

+28
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,31 @@
55

66
# Ignore Gradle build output directory
77
build
8+
9+
# Compiled class file
10+
*.class
11+
12+
# Log file
13+
*.log
14+
15+
# BlueJ files
16+
*.ctxt
17+
18+
# Mobile Tools for Java (J2ME)
19+
.mtj.tmp/
20+
21+
# Package Files #
22+
*.jar
23+
*.war
24+
*.nar
25+
*.ear
26+
*.zip
27+
*.tar.gz
28+
*.rar
29+
30+
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
31+
hs_err_pid*
32+
33+
.settings
34+
.project
35+
.classpath

TODO

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
TODO support multiple simultaneous profiling modes (profiling event types)
2+
- async-profiler dump format
3+
- ATM, the collapsed dump format (what we're using) is not supported for multiple-event profiling
4+
- can it be supported?
5+
- see
6+
- https://github.com/jvm-profiling-tools/async-profiler/issues/150
7+
- https://github.com/jvm-profiling-tools/async-profiler/issues/357
8+
- data flow changes
9+
- ATM, sample snapshots are uploaded specifying event-type-dependent parameters per snapshot
10+
- so multiple-event profiling needs a dedicated queue+snapshot+upload pipeline for each event type used simultaneously
11+
12+
TODO support per-thread profiling
13+
- use AsnycProfiler::execute
14+
- see
15+
- https://github.com/jvm-profiling-tools/async-profiler/issues/473
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.pyroscope.http;
2+
3+
public enum AggregationType {
4+
SUM ("sum"),
5+
AVERAGE ("average");
6+
7+
/**
8+
* Pyroscope aggregation type id, as expected by Pyroscope's HTTP API.
9+
*/
10+
public final String id;
11+
12+
AggregationType(String id) {
13+
this.id = id;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.pyroscope.http;
2+
3+
public enum Units {
4+
SAMPLES ("samples"),
5+
OBJECTS ("objects"),
6+
BYTES ("bytes");
7+
8+
/**
9+
* Pyroscope units id, as expected by Pyroscope's HTTP API.
10+
*/
11+
public final String id;
12+
13+
Units(String id) {
14+
this.id = id;
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.pyroscope.javaagent;
2+
3+
import java.util.EnumSet;
4+
import java.util.Optional;
5+
6+
import one.profiler.Events;
7+
import io.pyroscope.http.Units;
8+
import io.pyroscope.http.AggregationType;
9+
10+
public enum EventType {
11+
CPU (Events.CPU, Units.SAMPLES, AggregationType.SUM),
12+
ALLOC (Events.ALLOC, Units.OBJECTS, AggregationType.SUM),
13+
LOCK (Events.LOCK, Units.SAMPLES, AggregationType.SUM),
14+
WALL (Events.WALL, Units.SAMPLES, AggregationType.SUM),
15+
ITIMER (Events.ITIMER, Units.SAMPLES, AggregationType.SUM);
16+
17+
/**
18+
* Event type id, as defined in one.profiler.Events.
19+
*/
20+
public final String id;
21+
22+
/**
23+
* Unit option, as expected by Pyroscope's HTTP API.
24+
*/
25+
public final Units units;
26+
27+
/**
28+
* Aggregation type option, as expected by Pyroscope's HTTP API.
29+
*/
30+
public final AggregationType aggregationType;
31+
32+
EventType(String id, Units units, AggregationType aggregationType) {
33+
this.id = id;
34+
this.units = units;
35+
this.aggregationType = aggregationType;
36+
}
37+
38+
public static EventType fromId(String id) throws IllegalArgumentException {
39+
Optional<EventType> maybeEventType =
40+
EnumSet.allOf(EventType.class)
41+
.stream()
42+
.filter(eventType -> eventType.id.equals(id))
43+
.findAny();
44+
return maybeEventType.orElseThrow(IllegalArgumentException::new);
45+
}
46+
}

agent/src/main/java/io/pyroscope/javaagent/Profiler.java

+14-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class Profiler {
1616
private final Logger logger;
17-
private final String event;
17+
private final EventType eventType;
1818
private final Duration interval;
1919

2020
private static String libraryPath;
@@ -123,16 +123,18 @@ public static void init() {
123123

124124
private final AsyncProfiler instance = AsyncProfiler.getInstance(libraryPath);
125125

126+
// TODO this is actually start of snapshot, not profiling as a whole
126127
private Instant profilingStarted = null;
127128

128-
Profiler(final Logger logger, final String event, final Duration interval) {
129+
Profiler(final Logger logger, final EventType eventType, final Duration interval) {
129130
this.logger = logger;
130-
this.event = event;
131+
this.eventType = eventType;
131132
this.interval = interval;
132133
}
133134

135+
// TODO new method for starting new snapshot/batch
134136
final synchronized void start() {
135-
instance.start(event, interval.toNanos());
137+
instance.start(eventType.id, interval.toNanos());
136138
profilingStarted = Instant.now();
137139
logger.info("Profiling started");
138140
}
@@ -142,10 +144,16 @@ final synchronized Snapshot dump() {
142144
throw new IllegalStateException("Profiling is not started");
143145
}
144146

145-
final Snapshot result = new Snapshot(profilingStarted, Instant.now(), instance.dumpCollapsed(Counter.SAMPLES));
147+
final Snapshot result = new Snapshot(
148+
eventType,
149+
profilingStarted,
150+
Instant.now(),
151+
instance.dumpCollapsed(Counter.SAMPLES)
152+
);
146153

154+
// TODO use `this.start()` or analogue
147155
profilingStarted = Instant.now();
148-
instance.start(event, interval.toNanos());
156+
instance.start(eventType.id, interval.toNanos());
149157
return result;
150158
}
151159
}

agent/src/main/java/io/pyroscope/javaagent/PyroscopeAgent.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public static void premain(final String agentArgs,
4040
}
4141

4242
try {
43-
final Profiler profiler = new Profiler(logger, Events.ITIMER, config.profilingInterval);
43+
final Profiler profiler = new Profiler(logger, config.profilingEvent, config.profilingInterval);
4444

4545
final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
4646
profiler.start();

agent/src/main/java/io/pyroscope/javaagent/Snapshot.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import java.time.Instant;
44

55
final class Snapshot {
6+
public final EventType eventType;
67
public final Instant started;
78
public final Instant finished;
89
public final String data;
910

10-
Snapshot(final Instant started, final Instant finished, final String data) {
11+
Snapshot(final EventType eventType, final Instant started, final Instant finished, final String data) {
12+
this.eventType = eventType;
1113
this.started = started;
1214
this.finished = finished;
1315
this.data = data;

agent/src/main/java/io/pyroscope/javaagent/Uploader.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ private void uploadSnapshot(final Snapshot snapshot) throws InterruptedException
6868
private URL urlForSnapshot(final Snapshot snapshot) {
6969
try {
7070
final URI baseUri = URI.create(config.serverAddress);
71-
final String query = urlParam("name", config.applicationName)
71+
final String query = urlParam("name", config.timeseriesName)
72+
+ "&" + urlParam("units", snapshot.eventType.units.id)
73+
+ "&" + urlParam("aggregationType", snapshot.eventType.aggregationType.id)
74+
+ "&" + urlParam("sampleRate", Long.toString(config.profilingIntervalInHertz()))
7275
+ "&" + urlParam("from", Long.toString(snapshot.started.getEpochSecond()))
7376
+ "&" + urlParam("until", Long.toString(snapshot.finished.getEpochSecond()))
7477
+ "&" + urlParam("spyName", config.spyName);

agent/src/main/java/io/pyroscope/javaagent/config/Config.java

+40-19
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.pyroscope.javaagent.config;
22

3+
import io.pyroscope.javaagent.EventType;
34
import io.pyroscope.javaagent.PreConfigLogger;
4-
import one.profiler.Events;
55
import org.apache.logging.log4j.Level;
66

77
import java.nio.ByteBuffer;
@@ -21,38 +21,57 @@ public final class Config {
2121

2222
private static final String DEFAULT_SPY_NAME = "javaspy";
2323
private static final Duration DEFAULT_PROFILING_INTERVAL = Duration.ofMillis(10);
24-
private static final String DEFAULT_PROFILER_EVENT = Events.ITIMER;
24+
private static final EventType DEFAULT_PROFILER_EVENT = EventType.ITIMER;
2525
private static final Duration DEFAULT_UPLOAD_INTERVAL = Duration.ofSeconds(10);
2626
private static final String DEFAULT_SERVER_ADDRESS = "http://localhost:4040";
2727

2828
public final String spyName = DEFAULT_SPY_NAME;
2929
public final String applicationName;
3030
public final Duration profilingInterval;
31-
public final String profilingEvent;
31+
public final EventType profilingEvent;
3232
public final Duration uploadInterval;
3333
public final Level logLevel;
3434
public final String serverAddress;
3535
public final String authToken;
36+
public final String timeseriesName;
3637

3738
Config(final String applicationName,
3839
final Duration profilingInterval,
39-
final String profilingEvent,
40+
final EventType profilingEvent,
4041
final Duration uploadInterval,
4142
final Level logLevel,
4243
final String serverAddress,
43-
final String authToken) {
44+
final String authToken
45+
) {
4446
this.applicationName = applicationName;
4547
this.profilingInterval = profilingInterval;
4648
this.profilingEvent = profilingEvent;
4749
this.uploadInterval = uploadInterval;
4850
this.logLevel = logLevel;
4951
this.serverAddress = serverAddress;
5052
this.authToken = authToken;
53+
this.timeseriesName = timeseriesName(applicationName, profilingEvent);
54+
}
55+
56+
public long profilingIntervalInHertz() {
57+
return durationToHertz(this.profilingInterval);
58+
}
59+
60+
private static long durationToHertz(Duration duration) {
61+
Duration oneSecond = Duration.ofSeconds(1);
62+
return oneSecond.toNanos() / duration.toNanos();
5163
}
5264

5365
public static Config build() {
54-
return new Config(applicationName(), profilingInterval(),
55-
profilingEvent(), uploadInterval(), logLevel(), serverAddress(), authToken());
66+
return new Config(
67+
applicationName(),
68+
profilingInterval(),
69+
profilingEvent(),
70+
uploadInterval(),
71+
logLevel(),
72+
serverAddress(),
73+
authToken()
74+
);
5675
}
5776

5877
private static String applicationName() {
@@ -88,22 +107,24 @@ private static Duration profilingInterval() {
88107
}
89108
}
90109

91-
private static String profilingEvent() {
92-
final String profilingIntervalStr = System.getenv(PYROSCOPE_PROFILER_EVENT_CONFIG);
93-
if (profilingIntervalStr == null || profilingIntervalStr.isEmpty()) {
110+
private String timeseriesName(String applicationName, EventType eventType) {
111+
return applicationName + "." + eventType.id;
112+
}
113+
114+
private static EventType profilingEvent() {
115+
final String profilingEventStr =
116+
System.getenv(PYROSCOPE_PROFILER_EVENT_CONFIG);
117+
if (profilingEventStr == null || profilingEventStr.isEmpty()) {
94118
return DEFAULT_PROFILER_EVENT;
95119
}
96120

97-
final String profilingIntervalStrLC = profilingIntervalStr.toLowerCase(Locale.ROOT);
98-
if (profilingIntervalStrLC.equals(Events.ITIMER)
99-
|| profilingIntervalStrLC.equals(Events.CPU)
100-
|| profilingIntervalStrLC.equals(Events.ALLOC)
101-
|| profilingIntervalStrLC.equals(Events.LOCK)
102-
|| profilingIntervalStrLC.equals(Events.WALL)) {
103-
return profilingIntervalStr;
104-
} else {
121+
final String lowerCaseTrimmed = profilingEventStr.trim().toLowerCase();
122+
123+
try {
124+
return EventType.fromId(lowerCaseTrimmed);
125+
} catch (IllegalArgumentException e) {
105126
PreConfigLogger.LOGGER.warn("Invalid {} value {}, using {}",
106-
PYROSCOPE_PROFILER_EVENT_CONFIG, profilingIntervalStrLC, DEFAULT_PROFILER_EVENT);
127+
PYROSCOPE_PROFILER_EVENT_CONFIG, profilingEventStr, DEFAULT_PROFILER_EVENT.id);
107128
return DEFAULT_PROFILER_EVENT;
108129
}
109130
}

0 commit comments

Comments
 (0)