diff --git a/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java b/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java new file mode 100644 index 000000000..179006faa --- /dev/null +++ b/src/main/java/dan200/computercraft/core/tracking/ComputerMBean.java @@ -0,0 +1,155 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.core.tracking; + +import com.google.common.base.CaseFormat; +import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.computer.Computer; +import net.minecraft.util.text.LanguageMap; + +import javax.annotation.Nonnull; +import javax.management.*; +import java.lang.management.ManagementFactory; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.LongSupplier; + +public final class ComputerMBean implements DynamicMBean, Tracker +{ + private static final Set SKIP = new HashSet<>( Arrays.asList( + TrackingField.TASKS, TrackingField.TOTAL_TIME, TrackingField.AVERAGE_TIME, TrackingField.MAX_TIME, + TrackingField.SERVER_COUNT, TrackingField.SERVER_TIME + ) ); + + private static ComputerMBean instance; + + private final Map attributes = new HashMap<>(); + private final Map values = new HashMap<>(); + private final MBeanInfo info; + + private ComputerMBean() + { + List attributes = new ArrayList<>(); + for( Map.Entry field : TrackingField.fields().entrySet() ) + { + if( SKIP.contains( field.getValue() ) ) continue; + + String name = CaseFormat.LOWER_UNDERSCORE.to( CaseFormat.LOWER_CAMEL, field.getKey() ); + add( name, field.getValue(), attributes, null ); + } + + add( "task", TrackingField.TOTAL_TIME, attributes, TrackingField.TASKS ); + add( "serverTask", TrackingField.SERVER_TIME, attributes, TrackingField.SERVER_COUNT ); + + this.info = new MBeanInfo( + ComputerMBean.class.getSimpleName(), + "metrics about all computers on the server", + attributes.toArray( new MBeanAttributeInfo[0] ), null, null, null + ); + } + + public static void register() + { + try + { + ManagementFactory.getPlatformMBeanServer().registerMBean( instance = new ComputerMBean(), new ObjectName( "dan200.computercraft:type=Computers" ) ); + } + catch( InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException | MalformedObjectNameException e ) + { + ComputerCraft.log.warn( "Failed to register JMX bean", e ); + } + } + + public static void registerTracker() + { + if( instance != null ) Tracking.add( instance ); + } + + @Override + public Object getAttribute( String attribute ) throws AttributeNotFoundException, MBeanException, ReflectionException + { + LongSupplier value = attributes.get( attribute ); + if( value == null ) throw new AttributeNotFoundException(); + return value.getAsLong(); + } + + @Override + public void setAttribute( Attribute attribute ) throws InvalidAttributeValueException + { + throw new InvalidAttributeValueException( "Cannot set attribute" ); + } + + @Override + public AttributeList getAttributes( String[] attributes ) + { + return null; + } + + @Override + public AttributeList setAttributes( AttributeList attributes ) + { + return new AttributeList(); + } + + @Override + public Object invoke( String actionName, Object[] params, String[] signature ) throws MBeanException, ReflectionException + { + return null; + } + + @Override + @Nonnull + public MBeanInfo getMBeanInfo() + { + return info; + } + + @Override + public void addTaskTiming( Computer computer, long time ) + { + addValue( computer, TrackingField.TOTAL_TIME, time ); + } + + @Override + public void addServerTiming( Computer computer, long time ) + { + addValue( computer, TrackingField.SERVER_TIME, time ); + } + + @Override + public void addValue( Computer computer, TrackingField field, long change ) + { + Counter counter = values.get( field ); + counter.value.addAndGet( change ); + counter.count.incrementAndGet(); + } + + private MBeanAttributeInfo addAttribute( String name, String description, LongSupplier value ) + { + attributes.put( name, value ); + return new MBeanAttributeInfo( name, "long", description, true, false, false ); + } + + private void add( String name, TrackingField field, List attributes, TrackingField count ) + { + Counter counter = new Counter(); + values.put( field, counter ); + + String prettyName = LanguageMap.getInstance().getOrDefault( field.translationKey() ); + attributes.add( addAttribute( name, prettyName, counter.value::longValue ) ); + if( count != null ) + { + String countName = LanguageMap.getInstance().getOrDefault( count.translationKey() ); + attributes.add( addAttribute( name + "Count", countName, counter.count::longValue ) ); + } + } + + private static class Counter + { + AtomicLong value = new AtomicLong(); + AtomicLong count = new AtomicLong(); + } +} diff --git a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java index 858cb3729..4ed7433b6 100644 --- a/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java +++ b/src/main/java/dan200/computercraft/shared/proxy/ComputerCraftProxyCommon.java @@ -11,6 +11,7 @@ import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.network.wired.IWiredElement; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.core.computer.MainThread; +import dan200.computercraft.core.tracking.ComputerMBean; import dan200.computercraft.core.tracking.Tracking; import dan200.computercraft.shared.command.CommandComputerCraft; import dan200.computercraft.shared.command.arguments.ArgumentSerializers; @@ -32,6 +33,8 @@ import net.minecraft.inventory.container.Container; import net.minecraft.item.Item; import net.minecraft.item.MusicDiscItem; import net.minecraft.loot.*; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.util.ResourceLocation; import net.minecraft.util.registry.Registry; import net.minecraftforge.common.capabilities.CapabilityManager; @@ -46,6 +49,7 @@ import net.minecraftforge.fml.DeferredWorkQueue; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.server.FMLServerStartedEvent; +import net.minecraftforge.fml.event.server.FMLServerStartingEvent; import net.minecraftforge.fml.event.server.FMLServerStoppedEvent; import net.minecraftforge.items.CapabilityItemHandler; @@ -161,12 +165,23 @@ public final class ComputerCraftProxyCommon CommandComputerCraft.register( event.getDispatcher() ); } + @SubscribeEvent + public static void onServerStarting( FMLServerStartingEvent event ) + { + MinecraftServer server = event.getServer(); + if( server instanceof DedicatedServer && ((DedicatedServer) server).getProperties().enableJmxMonitoring ) + { + ComputerMBean.register(); + } + } + @SubscribeEvent public static void onServerStarted( FMLServerStartedEvent event ) { ComputerCraft.serverComputerRegistry.reset(); WirelessNetwork.resetNetworks(); Tracking.reset(); + ComputerMBean.registerTracker(); } @SubscribeEvent