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

[telemetry] game play metric #3016

Merged
merged 8 commits into from
Jul 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions engine/src/main/java/org/terasology/config/TelemetryConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public class TelemetryConfig {

private boolean launchPopupDisabled;

private String telemetryDestination;

private String errorReportingDestination;

public boolean isTelemetryEnabled() {
return telemetryEnabled;
}
Expand All @@ -50,6 +54,22 @@ public void setLaunchPopupDisabled(boolean launchPopupDisabled) {
this.launchPopupDisabled = launchPopupDisabled;
}

public String getTelemetryDestination() {
return telemetryDestination;
}

public void setTelemetryDestination(String telemetryDestination) {
this.telemetryDestination = telemetryDestination;
}

public String getErrorReportingDestination() {
return errorReportingDestination;
}

public void setErrorReportingDestination(String errorReportingDestination) {
this.errorReportingDestination = errorReportingDestination;
}

public void setTelemetryAndErrorReportingEnable(boolean enabled) {
telemetryEnabled = enabled;
errorReportingEnabled = enabled;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.config.Config;
import org.terasology.config.TelemetryConfig;
import org.terasology.context.Context;
import org.terasology.engine.subsystem.EngineSubsystem;
import org.terasology.telemetry.Metrics;
import org.terasology.telemetry.TelemetryEmitter;
import org.terasology.telemetry.logstash.TelemetryLogstashAppender;

import java.net.MalformedURLException;
import java.net.URL;

/**
* This is a telemetry engine sub system.
* It will initialise all the telemetry stuff such as the {@link com.snowplowanalytics.snowplow.tracker.emitter.Emitter} and configure the {@link org.terasology.telemetry.logstash.TelemetryLogstashAppender}.
Expand All @@ -41,8 +43,6 @@ public class TelemetrySubSystem implements EngineSubsystem {

private Emitter emitter;

private Context context;

@Override
public String getName() {
return "Telemetry";
Expand All @@ -51,8 +51,6 @@ public String getName() {
@Override
public void preInitialise(Context rootContext) {

context = rootContext;

// Add metrics to context, this helps show metric values in ui
metrics = new Metrics();
rootContext.put(Metrics.class, metrics);
Expand All @@ -68,24 +66,40 @@ public void postInitialise(Context rootContext) {
metrics.initialise(rootContext);

addTelemetryLogstashAppender(rootContext);
setTelemetryDestinationIfEnable(rootContext);
}

private void addTelemetryLogstashAppender(Context rootContext) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
TelemetryLogstashAppender telemetryLogstashAppender = new TelemetryLogstashAppender(rootContext);
lc.getLogger(Logger.ROOT_LOGGER_NAME).addAppender(telemetryLogstashAppender);
}

@Override
public void preShutdown() {

// TODO: REMOVE this when the telemetry is ready to users
Config config = context.get(Config.class);
TelemetryConfig telemetryConfig = config.getTelemetryConfig();
telemetryConfig.setTelemetryEnabled(false);
telemetryConfig.setErrorReportingEnabled(false);
Config config = rootContext.get(Config.class);
if (config.getTelemetryConfig().isErrorReportingEnabled()) {
String errorReportingDestination = config.getTelemetryConfig().getErrorReportingDestination();
if (errorReportingDestination != null) {
telemetryLogstashAppender.addDestination(errorReportingDestination);
telemetryLogstashAppender.start();
}
}
}

private void setTelemetryDestinationIfEnable(Context rootContext) {
Config config = rootContext.get(Config.class);
if (config.getTelemetryConfig().isTelemetryEnabled()) {
String telemetryDestination = config.getTelemetryConfig().getTelemetryDestination();
if (telemetryDestination != null) {
try {
URL url = new URL(telemetryDestination);
TelemetryEmitter telemetryEmitter = (TelemetryEmitter) emitter;
telemetryEmitter.changeUrl(url);
} catch (MalformedURLException e) {
logger.error("URL malformed", e);
}
}
}
}

@Override
public void shutdown() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,62 @@
import org.terasology.entitySystem.systems.BaseComponentSystem;
import org.terasology.entitySystem.systems.RegisterMode;
import org.terasology.entitySystem.systems.RegisterSystem;
import org.terasology.logic.characters.CharacterComponent;
import org.terasology.telemetry.GamePlayStatsComponent;
import org.terasology.world.block.BlockComponent;

import java.util.Map;

@RegisterSystem(RegisterMode.AUTHORITY)
public class EntityDestructionAuthoritySystem extends BaseComponentSystem {
@ReceiveEvent
public void onDestroy(DestroyEvent event, EntityRef entity) {
recordDestroyed(event,entity);
BeforeDestroyEvent destroyCheck = new BeforeDestroyEvent(event.getInstigator(), event.getDirectCause(), event.getDamageType());
entity.send(destroyCheck);
if (!destroyCheck.isConsumed()) {
entity.send(new DoDestroyEvent(event.getInstigator(), event.getDirectCause(), event.getDamageType()));
entity.destroy();
}
}

private void recordDestroyed(DestroyEvent event, EntityRef entityRef) {
EntityRef instigator = event.getInstigator();
if(entityRef.hasComponent(BlockComponent.class)) {
BlockComponent blockComponent = entityRef.getComponent(BlockComponent.class);
String blockName = blockComponent.getBlock().getDisplayName();
if (instigator.hasComponent(GamePlayStatsComponent.class)) {
GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
if (blockDestroyedMap.containsKey(blockName)) {
blockDestroyedMap.put(blockName, blockDestroyedMap.get(blockName) + 1);
} else {
blockDestroyedMap.put(blockName, 1);
}
instigator.saveComponent(gamePlayStatsComponent);
} else {
GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
blockDestroyedMap.put(blockName, 1);
instigator.addOrSaveComponent(gamePlayStatsComponent);
}
} else if (entityRef.hasComponent(CharacterComponent.class)) {
String monsterName = entityRef.getParentPrefab().getName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for later: maybe this shouldn't be "monster" specific since it would cover all kinds of things. A deer isn't really a monster. Would killing another player count?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Cervator , sorry about the delay :) not tested but I think that killing another player count. Do you think "creature" would be better as a name ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very belated reply from me: yes IMHO "creature" is better :-)

if (instigator.hasComponent(GamePlayStatsComponent.class)) {
GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
Map<String, Integer> monsterKilled = gamePlayStatsComponent.monsterKilled;
if (monsterKilled.containsKey(monsterName)) {
monsterKilled.put(monsterName, monsterKilled.get(monsterName) + 1);
} else {
monsterKilled.put(monsterName, 1);
}
instigator.saveComponent(gamePlayStatsComponent);
} else {
GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
Map<String, Integer> monsterKilled = gamePlayStatsComponent.monsterKilled;
monsterKilled.put(monsterName, 1);
instigator.addOrSaveComponent(gamePlayStatsComponent);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.terasology.rendering.nui.databinding.ReadOnlyBinding;
import org.terasology.rendering.nui.widgets.ActivateEventListener;
import org.terasology.rendering.nui.widgets.UIButton;
import org.terasology.rendering.nui.widgets.UILabel;
import org.terasology.rendering.nui.widgets.UIText;

import java.util.function.Consumer;
Expand All @@ -40,6 +41,7 @@ public class AddServerPopup extends CoreScreenLayer {
private UIButton okButton;
private UIButton cancelButton;
private ServerInfo serverInfo;
private UILabel tip;

private Consumer<ServerInfo> successFunc;

Expand All @@ -51,6 +53,7 @@ public void initialise() {
portText = find("port", UIText.class);
okButton = find("ok", UIButton.class);
cancelButton = find("cancel", UIButton.class);
tip = find("tip", UILabel.class);

okButton.subscribe(button -> {

Expand Down Expand Up @@ -159,4 +162,8 @@ public void onSuccess(Consumer<ServerInfo> success) {
public void onCancel(ActivateEventListener listener) {
cancelButton.subscribe(listener);
}

public void removeTip() {
tip.setVisible(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2017 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.telemetry;

import org.terasology.entitySystem.Component;
import org.terasology.network.Replicate;

import java.util.HashMap;
import java.util.Map;

/**
* A component stocks game play stats such as blocks destroyed, blocks placed, etc.
*/
public class GamePlayStatsComponent implements Component {

@Replicate
public Map<String, Integer> blockDestroyedMap = new HashMap<>();

@Replicate
public Map<String, Integer> blockPlacedMap = new HashMap<>();

@Replicate
public float distanceTraveled;

@Replicate
public float playTimeMinute;

@Replicate
public Map<String, Integer> monsterKilled = new HashMap<>();
}
55 changes: 54 additions & 1 deletion engine/src/main/java/org/terasology/telemetry/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.context.Context;
import org.terasology.telemetry.metrics.BlockDestroyedMetric;
import org.terasology.telemetry.metrics.BlockPlacedMetric;
import org.terasology.telemetry.metrics.GameConfigurationMetric;
import org.terasology.telemetry.metrics.GamePlayMetric;
import org.terasology.telemetry.metrics.Metric;
import org.terasology.telemetry.metrics.ModulesMetric;
import org.terasology.telemetry.metrics.MonsterKilledMetric;
import org.terasology.telemetry.metrics.SystemContextMetric;

import java.lang.reflect.Field;
Expand All @@ -37,15 +42,43 @@ public class Metrics {

private ModulesMetric modulesMetric;

private GameConfigurationMetric gameConfigurationMetric;

private BlockDestroyedMetric blockDestroyedMetric;

private BlockPlacedMetric blockPlacedMetric;

private GamePlayMetric gamePlayMetric;

private MonsterKilledMetric monsterKilledMetric;

public Metrics() {

}

public void initialise(Context context) {

systemContextMetric = new SystemContextMetric();

modulesMetric = new ModulesMetric(context);
gameConfigurationMetric = new GameConfigurationMetric(context);
blockDestroyedMetric = new BlockDestroyedMetric();
blockPlacedMetric = new BlockPlacedMetric();
gamePlayMetric = new GamePlayMetric();
monsterKilledMetric = new MonsterKilledMetric();
}

public void refreshAllMetrics() {
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.getType().getSuperclass() == Metric.class) {
try {
Metric metric = (Metric) field.get(this);
metric.getFieldValueMap();
} catch (IllegalAccessException e) {
logger.error("The field is not inaccessible: ", e);
}
}
}
}

public SystemContextMetric getSystemContextMetric() {
Expand All @@ -56,6 +89,26 @@ public ModulesMetric getModulesMetric() {
return modulesMetric;
}

public GameConfigurationMetric getGameConfigurationMetric() {
return gameConfigurationMetric;
}

public BlockDestroyedMetric getBlockDestroyedMetric() {
return blockDestroyedMetric;
}

public BlockPlacedMetric getBlockPlacedMetric() {
return blockPlacedMetric;
}

public MonsterKilledMetric getMonsterKilledMetric() {
return monsterKilledMetric;
}

public GamePlayMetric getGamePlayMetric() {
return gamePlayMetric;
}

public Optional<Metric> getMetric(Class<?> cl) {
Optional<Metric> optional = Optional.ofNullable(null);
Field[] fields = this.getClass().getDeclaredFields();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ public class TelemetryEmitter extends BatchEmitter {

public static final String DEFAULT_COLLECTOR_HOST = "utility.terasology.org";

public static final int DEFAULT_COLLECTOR_PORT = 80;
public static final String DEFAULT_COLLECTOR_OWNER = "Terasology Community";

public static final int DEFAULT_COLLECTOR_PORT = 14654;

private static final Logger logger = LoggerFactory.getLogger(TelemetryEmitter.class);

Expand Down
28 changes: 28 additions & 0 deletions engine/src/main/java/org/terasology/telemetry/TelemetryParams.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
package org.terasology.telemetry;

import com.snowplowanalytics.snowplow.tracker.DevicePlatform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.security.MessageDigest;
import java.util.Base64;

/**
* Terasology desktop game parameters for telemetry. They are needed by snowplow stacks.
Expand All @@ -25,4 +32,25 @@ public class TelemetryParams {
public static final String APP_ID_TERASOLOGY = "terasology";

public static final DevicePlatform PLATFORM_DESKTOP = DevicePlatform.Desktop;

/**
* The user id is based on user's MAC address. Normally it differs from one to another.
* We hash the MAC address to protect personnel information.
*/
public static String userId;

private static final Logger logger = LoggerFactory.getLogger(TelemetryParams.class);

static {
try {
InetAddress address = InetAddress.getLocalHost();
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
byte[] macAddress = networkInterface.getHardwareAddress();
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(macAddress);
userId = Base64.getEncoder().withoutPadding().encodeToString(hash);
} catch (Exception e) {
logger.error("Exception when getting MAC address", e);
}
}
}