Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Read-only File System #1945

Merged
merged 19 commits into from
Nov 4, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ public class ServiceProfilerServiceConfig {

private final URL serviceProfilerFrontEndPoint;

// Enable entire service profiler subsystem
private final boolean enabled;

// Either an inbuilt profile as defined in ProfileTypes, or a path to a custom JFC file to use for
// memory profiling
private final String memoryTriggeredSettings;
Expand All @@ -61,15 +58,13 @@ public ServiceProfilerServiceConfig(
int periodicRecordingDuration,
int periodicRecordingInterval,
URL serviceProfilerFrontEndPoint,
boolean enabled,
String memoryTriggeredSettings,
String cpuTriggeredSettings,
File tempDirectory) {
this.configPollPeriod = configPollPeriod;
this.periodicRecordingDuration = periodicRecordingDuration;
this.periodicRecordingInterval = periodicRecordingInterval;
this.serviceProfilerFrontEndPoint = serviceProfilerFrontEndPoint;
this.enabled = enabled;
this.memoryTriggeredSettings = memoryTriggeredSettings;
this.cpuTriggeredSettings = cpuTriggeredSettings;
this.tempDirectory = tempDirectory;
Expand All @@ -95,10 +90,6 @@ public URL getServiceProfilerFrontEndPoint() {
return serviceProfilerFrontEndPoint;
}

public boolean enabled() {
return enabled;
}

public String memoryTriggeredSettings() {
return memoryTriggeredSettings;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,7 @@ public JfrProfilerService(

public Future<ProfilerService> initialize() {
CompletableFuture<ProfilerService> result = new CompletableFuture<>();
if (!config.enabled()) {
result.completeExceptionally(new IllegalStateException("Profiler disabled"));
return result;
}
if (initialised || !config.enabled()) {
if (initialised) {
result.complete(this);
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,20 @@ private void start(Instrumentation instrumentation) {
}

File javaTmpDir = new File(System.getProperty("java.io.tmpdir"));
File tmpDir = new File(javaTmpDir, "applicationinsights-java");
if (!tmpDir.exists() && !tmpDir.mkdirs()) {
throw new IllegalStateException("Could not create directory: " + tmpDir.getAbsolutePath());
boolean readOnlyFileSystem = false;
if (javaTmpDir.canRead() && !javaTmpDir.canWrite()) {
readOnlyFileSystem = true;
}

if (!readOnlyFileSystem) {
File tmpDir = new File(javaTmpDir, "applicationinsights-java");
if (!tmpDir.exists() && !tmpDir.mkdirs()) {
throw new IllegalStateException("Could not create directory: " + tmpDir.getAbsolutePath());
}
} else {
startupLogger.info(
"Detected running on a read-only file system, telemetry will not be stored to disk or retried later on sporadic network failures. If this is unexpected, please check that the process has write access to the temp directory: "
+ javaTmpDir.getAbsolutePath());
}

Configuration config = MainEntryPoint.getConfiguration();
Expand Down Expand Up @@ -178,14 +189,16 @@ private void start(Instrumentation instrumentation) {

Cache<String, String> ikeyEndpointMap = Cache.builder().setMaximumSize(100).build();
StatsbeatModule statsbeatModule = new StatsbeatModule(ikeyEndpointMap);
// TODO (heya) apply Builder design pattern to TelemetryClient
TelemetryClient telemetryClient =
new TelemetryClient(
config.customDimensions,
metricFilters,
ikeyEndpointMap,
statsbeatModule,
config.preview.authentication);
TelemetryClient.builder()
.setCustomDimensions(config.customDimensions)
.setMetricFilters(metricFilters)
.setIkeyEndpointMap(ikeyEndpointMap)
.setStatsbeatModule(statsbeatModule)
.setReadOnlyFileSystem(readOnlyFileSystem)
.setAadAuthentication(config.preview.authentication)
.build();

TelemetryClientInitializer.initialize(telemetryClient, config);
TelemetryClient.setActive(telemetryClient);

Expand All @@ -203,15 +216,23 @@ private void start(Instrumentation instrumentation) {
appIdSupplier = new AppIdSupplier(telemetryClient);
AiAppId.setSupplier(appIdSupplier);

ProfilerServiceInitializer.initialize(
appIdSupplier::get,
SystemInformation.getProcessId(),
formServiceProfilerConfig(config.preview.profiler),
config.role.instance,
config.role.name,
telemetryClient,
formApplicationInsightsUserAgent(),
formGcEventMonitorConfiguration(config.preview.gcEvents));
if (config.preview.profiler.enabled) {
if (readOnlyFileSystem) {
throw new FriendlyException(
"Profile is not supported in a read-only file system.",
"disable profiler or use a writable file system");
}

ProfilerServiceInitializer.initialize(
appIdSupplier::get,
SystemInformation.getProcessId(),
formServiceProfilerConfig(config.preview.profiler),
config.role.instance,
config.role.name,
telemetryClient,
formApplicationInsightsUserAgent(),
formGcEventMonitorConfiguration(config.preview.gcEvents));
}

// this is for Azure Function Linux consumption plan support.
if ("java".equals(System.getenv("FUNCTIONS_WORKER_RUNTIME"))) {
Expand All @@ -232,7 +253,9 @@ private void start(Instrumentation instrumentation) {
statsbeatModule.start(telemetryClient, config);

// start local File purger scheduler task
LocalFilePurger.startPurging();
if (!readOnlyFileSystem) {
LocalFilePurger.startPurging();
}
}

private static GcEventMonitor.GcEventMonitorConfiguration formGcEventMonitorConfiguration(
Expand Down Expand Up @@ -265,7 +288,6 @@ private static ServiceProfilerServiceConfig formServiceProfilerConfig(
configuration.periodicRecordingDurationSeconds,
configuration.periodicRecordingIntervalSeconds,
serviceProfilerFrontEndPoint,
configuration.enabled,
configuration.memoryTriggeredSettings,
configuration.cpuTriggeredSettings,
tempDirectory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public static synchronized void initialize(
String userAgent,
GcEventMonitor.GcEventMonitorConfiguration gcEventMonitorConfiguration,
HttpPipeline httpPipeline) {
if (!initialized && config.enabled()) {
if (!initialized) {
initialized = true;
ProfilerServiceFactory factory = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public class TelemetryChannel {

private final HttpPipeline pipeline;
private final URL endpointUrl;
private final LocalFileWriter localFileWriter;
@Nullable private final LocalFileWriter localFileWriter;
// this is null for the statsbeat channel
@Nullable private final NetworkStatsbeat networkStatsbeat;

Expand Down Expand Up @@ -246,8 +246,10 @@ private CompletableResultCode internalSend(
}

private void writeToDiskOnFailure(List<ByteBuffer> byteBuffers) {
localFileWriter.writeToDisk(byteBuffers);
byteBufferPool.offer(byteBuffers);
if (localFileWriter != null) {
localFileWriter.writeToDisk(byteBuffers);
byteBufferPool.offer(byteBuffers);
}
}

private void parseResponseCode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,52 +94,36 @@ public class TelemetryClient {

private final Cache<String, String> ikeyEndpointMap;
private final StatsbeatModule statsbeatModule;
private final boolean readOnlyFileSystem;

@Nullable private final Configuration.AadAuthentication aadAuthentication;

private final Object channelInitLock = new Object();
private volatile @MonotonicNonNull BatchSpanProcessor channelBatcher;
private volatile @MonotonicNonNull BatchSpanProcessor statsbeatChannelBatcher;

// only used by tests
public TelemetryClient() {
this(
new HashMap<>(),
new ArrayList<>(),
Cache.builder().build(),
new StatsbeatModule(null),
null);
}

public TelemetryClient(
Map<String, String> customDimensions,
List<MetricFilter> metricFilters,
Cache<String, String> ikeyEndpointMap,
StatsbeatModule statsbeatModule,
@Nullable Configuration.AadAuthentication aadAuthentication) {
StringSubstitutor substitutor = new StringSubstitutor(System.getenv());
Map<String, String> globalProperties = new HashMap<>();
Map<String, String> globalTags = new HashMap<>();
for (Map.Entry<String, String> entry : customDimensions.entrySet()) {
String key = entry.getKey();
if (key.equals("service.version")) {
globalTags.put(
ContextTagKeys.AI_APPLICATION_VER.toString(), substitutor.replace(entry.getValue()));
} else {
globalProperties.put(key, substitutor.replace(entry.getValue()));
}
}
public static TelemetryClient.Builder builder() {
return new TelemetryClient.Builder();
}

globalTags.put(
ContextTagKeys.AI_INTERNAL_SDK_VERSION.toString(),
PropertyHelper.getQualifiedSdkVersionString());
// only used by tests
public static TelemetryClient createForTest() {
return builder()
.setCustomDimensions(new HashMap<>())
.setMetricFilters(new ArrayList<>())
.setIkeyEndpointMap(Cache.builder().build())
.setStatsbeatModule(new StatsbeatModule(null))
.build();
}

this.globalProperties = globalProperties;
this.globalTags = globalTags;
this.metricFilters = metricFilters;
this.ikeyEndpointMap = ikeyEndpointMap;
this.statsbeatModule = statsbeatModule;
this.aadAuthentication = aadAuthentication;
public TelemetryClient(Builder builder) {
this.globalTags = builder.globalTags;
this.globalProperties = builder.globalProperties;
this.metricFilters = builder.metricFilters;
this.ikeyEndpointMap = builder.ikeyEndpointMap;
this.statsbeatModule = builder.statsbeatModule;
this.readOnlyFileSystem = builder.readOnlyFileSystem;
this.aadAuthentication = builder.aadAuthentication;
}

public static TelemetryClient getActive() {
Expand Down Expand Up @@ -218,22 +202,30 @@ public BatchSpanProcessor getChannelBatcher() {
if (channelBatcher == null) {
synchronized (channelInitLock) {
if (channelBatcher == null) {
LocalFileCache localFileCache = new LocalFileCache();
File telemetryFolder = LocalStorageUtils.getOfflineTelemetryFolder();
LocalFileLoader localFileLoader =
new LocalFileLoader(
localFileCache, telemetryFolder, statsbeatModule.getNonessentialStatsbeat());
LocalFileWriter localFileWriter =
new LocalFileWriter(
localFileCache, telemetryFolder, statsbeatModule.getNonessentialStatsbeat());
LocalFileLoader localFileLoader = null;
LocalFileWriter localFileWriter = null;
if (!readOnlyFileSystem) {
LocalFileCache localFileCache = new LocalFileCache();
File telemetryFolder = LocalStorageUtils.getOfflineTelemetryFolder();
localFileLoader =
new LocalFileLoader(
localFileCache, telemetryFolder, statsbeatModule.getNonessentialStatsbeat());
localFileWriter =
new LocalFileWriter(
localFileCache, telemetryFolder, statsbeatModule.getNonessentialStatsbeat());
}

TelemetryChannel channel =
TelemetryChannel.create(
endpointProvider.getIngestionEndpointUrl(),
localFileWriter,
ikeyEndpointMap,
statsbeatModule.getNetworkStatsbeat(),
aadAuthentication);
LocalFileSender.start(localFileLoader, channel);

if (!readOnlyFileSystem) {
LocalFileSender.start(localFileLoader, channel);
}
channelBatcher = BatchSpanProcessor.builder(channel).build();
}
}
Expand All @@ -245,20 +237,26 @@ public BatchSpanProcessor getStatsbeatChannelBatcher() {
if (statsbeatChannelBatcher == null) {
synchronized (channelInitLock) {
if (statsbeatChannelBatcher == null) {
LocalFileCache localFileCache = new LocalFileCache();
File statsbeatFolder = LocalStorageUtils.getOfflineStatsbeatFolder();
LocalFileLoader localFileLoader =
new LocalFileLoader(localFileCache, statsbeatFolder, null);
LocalFileWriter localFileWriter =
new LocalFileWriter(localFileCache, statsbeatFolder, null);
LocalFileLoader localFileLoader = null;
LocalFileWriter localFileWriter = null;
if (!readOnlyFileSystem) {
LocalFileCache localFileCache = new LocalFileCache();
File statsbeatFolder = LocalStorageUtils.getOfflineStatsbeatFolder();
localFileLoader = new LocalFileLoader(localFileCache, statsbeatFolder, null);
localFileWriter = new LocalFileWriter(localFileCache, statsbeatFolder, null);
}

TelemetryChannel channel =
TelemetryChannel.create(
endpointProvider.getStatsbeatEndpointUrl(),
localFileWriter,
ikeyEndpointMap,
null,
null);
LocalFileSender.start(localFileLoader, channel);

if (!readOnlyFileSystem) {
LocalFileSender.start(localFileLoader, channel);
}
statsbeatChannelBatcher = BatchSpanProcessor.builder(channel).build();
}
}
Expand Down Expand Up @@ -470,4 +468,68 @@ private void initTelemetry(
monitorBase.setBaseType(baseType);
monitorBase.setBaseData(data);
}

public static class Builder {

private Map<String, String> globalTags;
private Map<String, String> globalProperties;
private List<MetricFilter> metricFilters;
private Cache<String, String> ikeyEndpointMap;
private StatsbeatModule statsbeatModule;
private boolean readOnlyFileSystem;
@Nullable private Configuration.AadAuthentication aadAuthentication;

public Builder setCustomDimensions(Map<String, String> customDimensions) {
StringSubstitutor substitutor = new StringSubstitutor(System.getenv());
Map<String, String> globalProperties = new HashMap<>();
Map<String, String> globalTags = new HashMap<>();
for (Map.Entry<String, String> entry : customDimensions.entrySet()) {
String key = entry.getKey();
if (key.equals("service.version")) {
globalTags.put(
ContextTagKeys.AI_APPLICATION_VER.toString(), substitutor.replace(entry.getValue()));
} else {
globalProperties.put(key, substitutor.replace(entry.getValue()));
}
}

globalTags.put(
ContextTagKeys.AI_INTERNAL_SDK_VERSION.toString(),
PropertyHelper.getQualifiedSdkVersionString());

this.globalProperties = globalProperties;
this.globalTags = globalTags;

return this;
}

public Builder setMetricFilters(List<MetricFilter> metricFilters) {
this.metricFilters = metricFilters;
return this;
}

public Builder setIkeyEndpointMap(Cache<String, String> ikeyEndpointMap) {
this.ikeyEndpointMap = ikeyEndpointMap;
return this;
}

public Builder setStatsbeatModule(StatsbeatModule statsbeatModule) {
this.statsbeatModule = statsbeatModule;
return this;
}

public Builder setReadOnlyFileSystem(boolean readOnlyFileSystem) {
this.readOnlyFileSystem = readOnlyFileSystem;
return this;
}

public Builder setAadAuthentication(Configuration.AadAuthentication aadAuthentication) {
this.aadAuthentication = aadAuthentication;
return this;
}

public TelemetryClient build() {
return new TelemetryClient(this);
}
}
}
Loading