How to send custom graph data to MCStats every hour? - java

I have been working on a plugin and have gotten some really interesting data with it, I am trying to add a custom graph and have succeeded on getting the graph to appear with the name I set in code on MCStats.
My plugin is here and recreates the Dense Ores Mod.
I would like to send block mined data on an hourly basis. This is what I have in my onEnable so far:
try {
Metrics metrics = new Metrics(this);
Graph blocksMinedGraph = metrics.createGraph("Extra items from blocks");
blocksMinedGraph.addPlotter(new Metrics.Plotter("Coal Ore") {
#Override
public int getValue() {
return coalMined;
}
});
blocksMinedGraph.addPlotter(new Metrics.Plotter("Iron Ore") {
#Override
public int getValue() {
return ironMined;
}
});
metrics.start();
} catch (IOException e) {
getLogger().info(ANSI_RED + "Metrics have been unable to load for: DenseOres" + ANSI_RESET);
}
This has successfully created a new graph on my MCStats page called 'Extra items from blocks' although I have been unable to populate it thus far. I have tried but cannot work out how to send the data.
Connected to this question, when sending the data, will I have to keep a count of the values in a file somewhere so they persist between reloads and server restarts?

I appear to have solved it by placing the blocksMinedGraph.addPlotter(...) parts in an async repeating task.
Here is the code with the repeating task in place, the graphs on MCStats take forever to update though.
try {
Metrics metrics = new Metrics(this);
if (!metrics.isOptOut()) {
final Graph blocksMinedGraph = metrics.createGraph("Extra items from blocks (Since v2.3)");
Bukkit.getScheduler().runTaskTimerAsynchronously(this, new Runnable() {
public void run() {
getLogger().info("Graph data sent");
blocksMinedGraph.addPlotter(new Metrics.Plotter("Coal Ore") {
#Override
public int getValue() {
return coalMined;
}
});
blocksMinedGraph.addPlotter(new Metrics.Plotter("Iron Ore") {
#Override
public int getValue() {
return ironMined;
}
});
}
}, DELAY, INCREMENT);
getLogger().info("Metrics started");
metrics.start();
}
} catch (IOException e) {
getLogger().info(ANSI_RED + "Metrics have been unable to load for: DenseOres" + ANSI_RESET);
}

Related

How to programatically (java) prevent specific errors messages to be sent to Sentry

How to programatically (java) prevent specific errors messages to be sent to Sentry? I want, for example, do not send to Sentry errors with the word "any_example_word". It's important to know that filtering by error message is not enabled in the User Interface.
I'm using Sentry 1.7.23, but all examples I can find use latest version (4.*), which are tottaly different. They use classes and methods that do not exist in this old version.
I don't know if this is relevant, but my application runs over thorntail and it uses jdk 8.
Edit:
I'm trying to do this:
#WebListener
public class MyContextListener implements ServletContextListener {
private static SentryClient sentryClient = SentryClientFactory.sentryClient();
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
Sentry.init();
String testStrings = "ipsis litteris;some_error_message";
String[] messagesToIgnore = StringUtils.split(testStrings, ';');
sentryClient.addShouldSendEventCallback(new ShouldSendEventCallback() {
#Override
public boolean shouldSend(Event event) {
for (Map.Entry<String, SentryInterface> interfaceEntry : event.getSentryInterfaces().entrySet()) {
if (interfaceEntry.getValue() instanceof ExceptionInterface) {
ExceptionInterface i = (ExceptionInterface) interfaceEntry.getValue();
for (SentryException sentryException : i.getExceptions()) {
for (String msgToIgnore : messagesToIgnore) {
if (StringUtils.contains(sentryException.getExceptionMessage(), msgToIgnore)) {
return false;
}
}
}
}
}
return true;
}
});
Sentry.setStoredClient(sentryClient);
}
#Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
Question 1) Is this the correct place to initialize Sentry?
Question 2) Why ShouldSendEventCallback is lost? Looking at
io.sentry.SentryClient:
public void sendEvent(Event event) {
for (ShouldSendEventCallback shouldSendEventCallback : shouldSendEventCallbacks) {
if (!shouldSendEventCallback.shouldSend(event)) {
logger.trace("Not sending Event because of ShouldSendEventCallback: {}", shouldSendEventCallback);
return;
}
}
try {
connection.send(event);
} catch (LockedDownException | TooManyRequestsException e) {
logger.debug("Dropping an Event due to lockdown: " + event);
} catch (Exception e) {
logger.error("An exception occurred while sending the event to Sentry.", e);
} finally {
getContext().setLastEventId(event.getId());
}
}
In some point during app execution, sentryClient is reinitialized and shouldSendEventCallbacks becomes empty, what causes my messages not being filtered.
So I get back to question 1, since apparently sentry configuration is not being persistent.

How to wait until all of a series of nested CompletableFutures are done?

I have an Augmented Reality application where ARObject is a POJO:
class ARObject {
CompletableFuture<Texture> texture;
CompletableFuture<Material> material;
ModelRenderable renderable;
void setTexture(CompletableFuture<Texture> texture) {
this.texture = texture;
}
CompletableFuture<Texture> getTexture() {
return texture;
}
void setMaterial(CompletableFuture<Material> material) {
this.material = material;
}
CompletableFuture<Material> getMaterial() {
return material;
}
}
The scene is composed in real-time. During that procedure the Texture objects need to be built and then the Material objects based on the Texture objects. Once the Material is ready, then the ShapeFactory can be used to spawn the actual AR objects (as a form Renderable). That means that the build logic contains two CompletableFutures nested into each other for each AR object:
for (ARObject arObject : arObjects) {
Texture.Builder textureBuilder = Texture.builder();
textureBuilder.setSource(context, arObject.resourceId);
CompletableFuture<Texture> texturePromise = textureBuilder.build(); // Future #1
arObject.setTexture(texturePromise);
texturePromise.thenAccept(texture -> {
CompletableFuture<Material> materialPromise =
MaterialFactory.makeOpaqueWithTexture(context, texture); // Future #2
arObject.setMaterial(materialPromise);
});
}
One way to complete the scene build is to wait until all of the CompletableFutures are done, and then the ShapeFactory step can come.
I tried to use .get() on the Futures, but that would not just completely butcher the parallelism offered by the async calls, but it did also lock up the app because I assume it caused the wait on the UI thread.
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getTexture().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Texture CompletableFuture waiting problem " + e.toString());
}
});
Arrays.stream(arObjectList).forEach(a -> {
try {
a.getMaterial().get();
} catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Material CompletableFuture waiting problem " + e.toString());
}
});
I broke the build procedure up to several functions which call each other in a call chain. The chain is the following:
populateScene
afterTexturesLoaded
afterTexturesSet
waitForMaterials
afterMaterialsLoaded
private void afterMaterialsLoaded() {
// Step 3: composing scene objects
// Get a handler that can be used to post to the main thread
Handler mainHandler = new Handler(context.getMainLooper());
for (ARObject arObject : arObjectList) {
try {
Material textureMaterial = arObject.getMaterial().get();
RunnableShapeBuilder shapeBuilder = new RunnableShapeBuilder(arObject, this, textureMaterial);
mainHandler.post(shapeBuilder);
}
catch (ExecutionException | InterruptedException e) {
Log.e(TAG, "Scene populating exception " + e.toString());
}
}
}
private Long waitForMaterials() {
while (!Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null)) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
return 0L;
}
private void afterTexturesSet() {
boolean materialsDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getMaterial() != null && arObject.getMaterial().isDone());
// If any of the materials are not loaded, then recurse until all are loaded.
if (!materialsDone) {
CompletableFuture<Texture>[] materialPromises =
Stream.of(arObjectList).map(ARObject::getMaterial).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(materialPromises)
.thenAccept((Void aVoid) -> afterMaterialsLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterMaterialsLoaded();
}
}
private void afterTexturesLoaded() {
// Step 2: material loading
CompletableFuture materialsSetPromise = CompletableFuture.supplyAsync(this::waitForMaterials);
CompletableFuture.allOf(materialsSetPromise)
.thenAccept((Void aVoid) -> afterTexturesSet())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
}
/**
* Called when the AugmentedImage is detected and should be rendered. A Sceneform node tree is
* created based on an Anchor created from the image.
*/
#SuppressWarnings({"AndroidApiChecker", "FutureReturnValueIgnored"})
void populateScene() {
// Step 1: texture loading
boolean texturesDone = Stream.of(arObjectList).allMatch(arObject -> arObject.getTexture() != null && arObject.getTexture().isDone());
// If any of the textures are not loaded, then recurse until all are loaded.
if (!texturesDone) {
CompletableFuture<Texture>[] texturePromises =
Stream.of(arObjectList).map(ARObject::getTexture).toArray(CompletableFuture[]::new);
CompletableFuture.allOf(texturePromises)
.thenAccept((Void aVoid) -> afterTexturesLoaded())
.exceptionally(
throwable -> {
Log.e(TAG, "Exception building scene", throwable);
return null;
});
} else {
afterTexturesLoaded();
}
}
There are several problem with this. First: it still kind of botches the asynchronous nature. In an ideal situation a corresponding material texture pair would be independently loaded and generated from the other pairs. In this latest version there are many meeting points in the execution flow, which does not correspond to the ideal independent scenario. Second: I even could not avoid the waitForMaterials step where I have ugly Thread.sleep(). Third: the code still fails overall, because at the last step when it comes to finally build the shapes from the loaded textures and materials, I'm gifted with an error java.lang.IllegalStateException: Must be called from the UI thread.. Because of this I put in another twist: RunnableShapeBuilder. With that there's no exception, but still nothing is shown on the scene, while the code get even more complicated.
class RunnableShapeBuilder implements Runnable {
ARObject arObject;
AnchorNode parentNode;
Material textureMaterial;
RunnableShapeBuilder(ARObject arObject, AnchorNode parentNode, Material textureMaterial) {
this.arObject = arObject;
this.parentNode = parentNode;
this.textureMaterial = textureMaterial;
}
#Override
public void run() {
arObject.renderable = ShapeFactory.makeCube(
new Vector3(0.5f, 1, 0.01f),
new Vector3(0.0f, 0.0f, 0.0f),
textureMaterial
);
...
}
}
The answer is: I do not have to wait for these nested CompletableFutures. While my scenario got more complex I assumed I'd have to wait until construction of the 3D Material and Texture is complete. The problem was in an off-topic place: although I set the AR object's anchor. The AR Object's anchor is derived from the hit test, and I'd need to set the parent of the anchor to the AR Scene. This last step was lost during some refactorings, and nothing warns you if this happens, just nothing show up on the AR Scene.
Focusing on the question itself: I highly disadvise to try to wait on any because it'll lead to pain and suffering only. Seek for a different alley of solution.

How to download a Google Play Games Services Saved Games Snapshot's Data, silently?

I'm trying to use Google's Saved Games feature with Google Play Games Services in my Android app. Google provides sample code how to do so:
private static final int RC_SAVED_GAMES = 9009;
private String mCurrentSaveName = "snapshotTemp";
private void showSavedGamesUI() {
SnapshotsClient snapshotsClient =
Games.getSnapshotsClient(this, GoogleSignIn.getLastSignedInAccount(this));
int maxNumberOfSavedGamesToShow = 5;
Task<Intent> intentTask = snapshotsClient.getSelectSnapshotIntent(
"See My Saves", true, true, maxNumberOfSavedGamesToShow);
intentTask.addOnSuccessListener(new OnSuccessListener<Intent>() {
#Override
public void onSuccess(Intent intent) {
startActivityForResult(intent, RC_SAVED_GAMES);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent intent) {
if (intent != null) {
if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA)) {
// Load a snapshot.
SnapshotMetadata snapshotMetadata =
intent.getParcelableExtra(SnapshotsClient.EXTRA_SNAPSHOT_METADATA);
mCurrentSaveName = snapshotMetadata.getUniqueName();
// Load the game data from the Snapshot
// ...
} else if (intent.hasExtra(SnapshotsClient.EXTRA_SNAPSHOT_NEW)) {
// Create a new snapshot named with a unique string
String unique = new BigInteger(281, new Random()).toString(13);
mCurrentSaveName = "snapshotTemp-" + unique;
// Create the new snapshot
// ...
}
}
}
Obviously, Google wants you to use their provided intent to let the user decide which saved game to load or if a new save game should be created.
I, on the other hand, want to do this decision for the user. However, I'm unable to find a way to return a list of snapshots and to load snapshot data.
Since my game won't require to maintain more than one saved game per user I'm less interested in getting a list of snapshots without an intent (which would be an interesting solution, though) and more in loading a snapshot based on the name of the saved game, silently.
How can I load a snapshot without showing an intent?
The comment of jess leaded me to a solution that is now deprecated. However the person who posted the answer pointed out that there is also a working solution in the CollectAllTheStars sample app that is provided by Google. I was tempted to check this sample app to find out if the Google team has changed the code to fit the new way. For my amuse the comments in that sample app were describing the old deprecated way, still, but the code was changed for my luck.
Inspecting the code gave me ideas, so I came up with this solution:
String serializedSavedGameData;
public void downloadSavedGameData(final String name) {
if(snapshotsClient != null) {
snapshotsClient.open(name, true, SnapshotsClient.RESOLUTION_POLICY_MOST_RECENTLY_MODIFIED).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "Error while opening snapshot: ", e);
}
}).continueWith(new Continuation<SnapshotsClient.DataOrConflict<Snapshot>, byte[]>() {
#Override
public byte[] then(#NonNull Task<SnapshotsClient.DataOrConflict<Snapshot>> task) throws Exception {
Snapshot snapshot = task.getResult().getData();
// Opening the snapshot was a success and any conflicts have been resolved.
try {
// Extract the raw data from the snapshot.
return snapshot.getSnapshotContents().readFully();
} catch (IOException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
} catch (NullPointerException e) {
Log.e(TAG, "Error while reading snapshot: ", e);
}
return null;
}
}).addOnCompleteListener(new OnCompleteListener<byte[]>() {
#Override
public void onComplete(#NonNull Task<byte[]> task) {
if(task.isSuccessful()) {
byte[] data = task.getResult();
try {
serializedSavedGameData = new String(data, "UTF-16BE");
} catch (UnsupportedEncodingException e) {
Log.d(TAG, "Failed to deserialize save game data: " + e.getMessage());
}
} else {
Exception ex = task.getException();
Log.d(TAG, "Failed to load saved game data: " + (ex != null ? ex.getMessage() : "UNKNOWN"));
}
}
});
}
}
I implemented a simple resolving policy (take the newest saved game on conflicts), but I had no time to hard test all the different cases like conflicts, but it passed my simple tests so far.
Hopefully anybody can profit from this.

Send message with Telegram API and store session (NOT with bot API)

I want to implement a very simple Java Telegram Client, which is capable of sending and receiving messages and store the sessions across multiple starts. I already managed to authenticate and receive messages
api = new TelegramApi(apiState, new AppInfo(API_ID, "console", "1", "1", "en"), new ApiCallback() {
#Override
public void onAuthCancelled(TelegramApi api) {
Log.d(TAG, "-----------------CANCELLED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdatesInvalidated(TelegramApi api) {
Log.d(TAG, "-----------------INVALIDATED----------------");
Log.d(TAG, api.getApiContext().toString());
}
#Override
public void onUpdate(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------UPDATE----------------");
Log.d(TAG, tlAbsUpdates.toString());
if (tlAbsUpdates instanceof TLUpdateShortMessage) {
Log.d(TAG, "-----------------UPDATE CHAT MESSAGE----------------");
int senderId = ((TLUpdateShortMessage) tlAbsUpdates).getUserId();
Log.d(TAG, "Message from " + senderId);
String message = ((TLUpdateShortMessage) tlAbsUpdates).getMessage();
Log.d(TAG, message);
activity.appendMessage(TAG, message);
}
}
});
api.switchToDc(2);
TLConfig config = null;
try {
config = api.doRpcCallNonAuth(new TLRequestHelpGetConfig());
} catch (TimeoutException | IOException e) {
e.printStackTrace();
}
apiState.updateSettings(config);
However, I struggle to send messages to another user. For the beginning, it would be enough if I could send a message back to the user, who sent me a message before (by retrieving the senderId, as you can see in the onUpdate method before). However, if someone could also help me with retrieving the ids of my saved contacts, it would be perfect.
Furthermore, I want to store the sessions accross multiple startups, since I get a FLOOD_WAIT error (420), if I test my code to often.
For this I used https://github.com/rubenlagus/TelegramApi/blob/51713e9b6eb9e0ae0d4bbbe3d4deffff9b7f01e4/src/main/java/org/telegram/bot/kernel/engine/MemoryApiState.java and its used classes (e.g. TLPersistence), which stores and loads the ApiState. However, apparently it does not store the signin status, since I always have to authenticate my number every time I update the code.
By the way, I am using Api layer 66 (https://github.com/rubenlagus/TelegramApi/releases).
UPDATE 1:
Problems with sending messages solved myself:
private void sendMessageToUser(int userId, String message) {
TLInputPeerUser peer = new TLInputPeerUser();
peer.setUserId(userId);
TLRequestMessagesSendMessage messageRequest = new TLRequestMessagesSendMessage();
messageRequest.setFlags(0);
messageRequest.setPeer(peer);
messageRequest.setRandomId(new SecureRandom().nextLong());
messageRequest.setMessage(message);
api.doRpcCallNonAuth(messageRequest, 1500, new RpcCallback<TLAbsUpdates>() {
#Override
public void onResult(TLAbsUpdates tlAbsUpdates) {
Log.d(TAG, "-----------------------MESSAGE SENT-----------------------");
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "-----------------------MESSAGE SENT ERROR-----------------------");
Log.d(TAG, String.valueOf(i));
if(s != null) {
Log.d(TAG, s);
}
}
});
}
However, now I am stuck at finding the userIds of my contacts.
After first update this is left:
Saving the session state (and signin state)
Find userIds of contacts
Update 2:
I managed to fetch the users, with which there are already dialogs. This is enough for my use case, however, loading all contacts would be perfect. This is how to load users from existing dialogs:
private int getUserId(String phone) throws InterruptedException {
TLRequestMessagesGetDialogs dialogs = new TLRequestMessagesGetDialogs();
dialogs.setOffsetId(0);
dialogs.setLimit(20);
dialogs.setOffsetPeer(new TLInputPeerUser());
CountDownLatch latch = new CountDownLatch(1);
api.doRpcCallNonAuth(dialogs, 1500, new RpcCallback<TLAbsDialogs>() {
#Override
public void onResult(TLAbsDialogs tlAbsDialogs) {
Log.d(TAG, "----------------------getUsers--------------------");
for(TLAbsUser absUser : ((TLDialogs) tlAbsDialogs).getUsers()) {
users.add((TLUser) absUser);
}
latch.countDown();
}
#Override
public void onError(int i, String s) {
Log.d(TAG, "----------------------getUsers ERROR--------------------");
latch.countDown();
}
});
latch.await();
for(TLUser user : users) {
if(user.getPhone().equals(phone)) {
return user.getId();
}
}
return 0;
}
After second update this is left:
Saving the session state (and signin state)
Get user ids from contacts instead of dialogs

Share non-parcelable item between App and service

I'm looking for one way to share a non-parcelable item from my application and my current Service. This is the situation:
I have a Service to store all the media data from a camera application, photos, videos etc. The mission of this service is continue saving the media when the user go to background. When I did this in a first instance, I had a lot of SIGSEGV errors:
08-22 10:15:49.377 15784-15818/com.bq.camerabq A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x8c3f4000 in tid 15818 (CameraModuleBac)
This was because the Image item that I recover from my imageReaders are not parceable, I fix this saving the Bytebuffers from the image instead of the whole image item.
But, now I'm getting the same problem with DNG captures, because from my imageReader I got a CaptureResult item that I need to use to create a DngCreator item to write the dng image.
CaptureResults and DngCreators are not parcelables or Serializables, so I don't find a way to save my data from the application to recover it in the service if I'm in background.
I have tried to copy the reference when calling the Service and it didn't worked. Also I saw in other posts as Object Sharing Between Activities in Android and Object Sharing Between Activities in Android that I can save the item in a static reference in my application context to be able to recover it in different activities. So finally I tried this:
public class DngManager extends Application {
public static DngManager sDngManagerInstance;
protected Hashtable<String, CaptureResult> dngCaptureResults;
private static String DNG_KEY_PREFIX = "dngResult_";
public DngManager(){
super();
createDNGCaptureResults();
}
public void createDNGCaptureResults() {
dngCaptureResults = new Hashtable<String, CaptureResult>();
}
public boolean addDNGCaptureResultToSharedMem(long dateKey, CaptureResult value) {
dngCaptureResults.put(DNG_KEY_PREFIX + dateKey, value);
return true;
}
public CaptureResult getFromDNGCaptureResults(long dateKey) {
return dngCaptureResults.get(DNG_KEY_PREFIX + dateKey);
}
private boolean containsDNGCaptureResults(long dateKey) {
return dngCaptureResults.containsKey(DNG_KEY_PREFIX + dateKey);
}
public void clearDNGCaptureResults(long dateKey) {
String partKey = String.valueOf(dateKey);
Enumeration<String> e2 = dngCaptureResults.keys();
while (e2.hasMoreElements()) {
String i = (String) e2.nextElement();
if (i.contains(partKey))
dngCaptureResults.remove(i);
}
}
public static DngManager getInstance(){
if (sDngManagerInstance == null){
sDngManagerInstance = new DngManager();
}
return sDngManagerInstance;
}
}
And later I recover it in my service:
CaptureResult dngResult = ((DngManager)getApplication()).getFromDNGCaptureResults(mDngPicture.getDateTaken());
if (dngResult == null) {
return;
}
DngCreator dngCreator = new DngCreator(mCameraCharacteristics, dngResult);
path = Storage.generateFilepath(title, "dng");
file = new File(Uri.decode(path));
try {
Log.e(TAG, "[DngSaveTask|run] WriteByteBuffer: Height " + mDngPicture.getSize().getHeight() + " Width " + mDngPicture.getSize().getWidth());
OutputStream os = new FileOutputStream(file);
dngCreator.writeByteBuffer(os, mDngPicture.getSize(), mDngPicture.getDngByteBuffer(), 0);
} catch (IOException e) {
Log.d(TAG, "[DngSaveTask|run] " + e);
e.printStackTrace();
}
dngCreator.close();
Log.e(TAG, "[DngSaveTask|run] Cleaning Result from shared memory");
DngManager.getInstance().clearDNGCaptureResults(mDngPicture.getDateTaken());
MediaScannerConnection.scanFile(getApplicationContext(), new String[]{file.getAbsolutePath()}, null, null);
Anyway, it still giving me back a SIGSEGV error. What else can I try?

Categories