Comments inline below ...

On 6/3/18 6:50 PM, David Holmes wrote:
Hi Gary,

Thanks for the further explanations, I have a much better view of things now. To summarise: - The VM initializes the perfMemory subsystem during early VM initialization
- If !UsePerfdata then perfMemory is not initialized.
- The initialization state of the perfMemory system is exposed to the Java Perf class via a local initialization flag set at class init time - The Perf class API checks for initialization (either at the Java level, or in native code) and throws IOException if not initialized.
- The users of the Perf class can check for IOExceptions.

There was a question as to whether the Perf API should deal with the initialization issue and throw exceptions, or whether the obtaining of the Perf instance should throw the exception. I think the API level is fine as the time related parts of the API still function even if the rest doesn't.

Looking at the changes again ...

I see four clients of the Perf instance:

1. ./java.base/share/classes/jdk/internal/perf/PerfCounter.java

This is the primary API used by the rest of the JDK libraries to interact with the "perf" systems. The counters must not throw any exceptions if the Perf system is not available due to !UsePerfData (which they don't). It's unclear how users of the counters may be impacted if the "counts" always return zero ??

Suggestion: it might be useful if PerfCounter.toString showed if the Perf system was disabled e.g.

return name + (perf.isInitialized() ? "" : "[Disabled]" + " = " + get();

I'll include this in the next webrev.
I'll also make sure no one has a dependency on the PerfCounter.toString formatting.



2. ./java.management/share/classes/sun/management/VMManagementImpl.java

This code is not modified by your webrev but seems to have its own expectations about the impact of !UsePerfdata:

       } catch (IllegalArgumentException e) {
            // If the shared memory doesn't exist e.g. if -XX:-UsePerfData
            // was set
            noPerfData = true;
        } catch (IOException e) {
            throw new AssertionError(e);
        }

I can't see where/why this code thinks that -UsePerfData leads to IllegalArgumentException, but your throwing of IOException is going to break it badly! This needs further investigation.
Yes, I saw that stray wrong comment earlier and was wondering what to do with it.

In ConnectorAddressLink importFrom there is a sequence where a caught IllegalArgumentException
is converted to an IOException from a failed attach().

The Perf.attach() only throws IllegalArgumentException when the rw or ro mode is not passed correctly.
Or when the platform code detects a missing process or permissions error.
See open/src/hotspot/os<platform>/perfMemory_<platform>.cpp

The IOException is a catch all for any other error case, such as stat not being able
to determine the size of the mapped shared memory file.

The sequence in VMManagementImpl.java seems to imply the IllegalArgiumentException
cases are fine to ignore, but the IOException is worth an exception.

I need to follow that Assertion to see how it is used.




3. ./jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/monitor/protocol/local/PerfDataBuffer.java

This class throws exceptions on any attach failure so in that sense the IOException doesn't change that, however it does appear that this class is intended to throw MonitorException if things go wrong. More generally we should look at how the jvmstat tool(s?) are used and whether they are reporting the lack of UsePerfData in a user-friendly way.
Agreed I need to follow up on the other higher level usages.

4. ./jdk.management.agent/share/classes/jdk/internal/agent/ConnectorAddressLink.java

I'm not at all clear on the use of this class so it is hard to assess the change. The throwing of IOException from the "import" function is fine, but the silent inaction of the "export" functions is less clear.

---

On the issue of IOException and message ... I think the native code that throws IOException when not-initialized should use the same message as the Java code. That's the only consistent thing to do IMHO. It would also be helpful to the user if the exception message could tie back to the -UsePerfData setting. I don't normally suggest things like this but these failures (as we found out in SE Embedded days when we did turn this off!) can be somewhat perplexing to the end user. To that end perhaps:

throw new IOException("Perf memory is not initialized - check UsePerfData setting");
I'm Ok with "Perf memory is not initialized", but uncomfortable with the "check UsePerfData setting". Obviously, there would be a disconnect between the disabling of the feature and the
application attempting to use the capability.

---

On the meta questions ... it seems the ability to disable UsePerfData can have a significant affect on startup so may be relied upon by some users. So any change to this would need to go through a rigorous process to determine impact. So I'd say that's future work beyond the scope of the current RFE/bug.

Thanks,
David

On 1/06/2018 11:06 PM, Gary Adams wrote:
Comments inline ...

On 6/1/18, 7:45 AM, Gary Adams wrote:
PS.

PerfCounter.java

lb field should be left as final. You can work around
definite-assignment problems by using a local variable and only
assigning to the field after the catch block:

       private PerfCounter(String name, int type) {
            this.name = name;
+         LongBuffer temp = null;
+         try {
                ByteBuffer bb = perf.createLong(name, type, U_None, 0L);
                bb.order(ByteOrder.nativeOrder());
+             temp = bb.asLongBuffer();
+         } catch (IOException e) {
+             // may not be available
+         }
+         this.lb = temp;
        }
Thanks for the tip. The PerfCounter changes were the last thing I dropped in after adding the IOException and fixing the places where things did not compile.

Fundamentally, what sort of things will break, when the previous interface
did not expose the possibility that the perf memory was not initialized
as assumed in the earlier code. Does the upper level code "have to"
be aware, or is the contract to do a "best effort" if something lower down
is not as expected.

Hi Gary,

Meta-question partially raised in the the recent bug report comments: do
we care about this any more? Does anything need to disable UsePerfData?
Can we not deprecate it in 11 then obsolete in 12? That would be a lot
simpler.
My own bias is that many of the motivations for faster startup and smaller footprint are still useful goals. If not for embedded systems, then for container environments.
A process started with -XX:-UsePerfData does not support external attach
and does not maintain the internal variables for the self attach.


That aside ...

It's hard to see how all the different paths in these related API's
stitch together - especially when you have to consider the API being
used on the current VM or a different one. I'm not at all clear what the
control flow is, so it is hard to see exactly how the initialization
issue is being handled. As I wrote in the initial report it is unclear
exactly which parts of the "perf" subsystem are impacted by the value of
UsePerfData.
Starting with the Perf.java class initialization, I added the exposure of
the PerfMemory::is_initialized().

Perf.java:

559 private native boolean isInitialized();
560
  561     private static native void registerNatives();
  562
  563     static {
  564         registerNatives();
  565         instance = new Perf();
566 initialized = instance.isInitialized();
  567     }

perf.cpp:
298 PERF_ENTRY(jboolean, Perf_IsInitialized(JNIEnv *env, jobject perf))
299
300 PerfWrapper("Perf_IsInitialized");
301
302 return PerfMemory::is_initialized();
303
304 PERF_END


 From the VM perspective UsePerfData is both a command line argument
and a flag to be used whenever that information is needed. There are
scattered checks to deal with setup and shutdown, but also the runtime
fulfillment of the PerfMemory data structures.

Looking at perfMemory_init() and perfMemory_exit(), I concluded that
the UsePerfData flag essentially noops all of the perfMemory allocation and updating. The only information required in the upper layers of the Perf.java
code is the fact that the perf memory is not initialized.

perfMemory.cpp:

     58    void perfMemory_init() {
     59
     60      if (!UsePerfData) return;
     61
     62      PerfMemory::initialize();
     63    }
     64
     65    void perfMemory_exit() {
     66
     67      if (!UsePerfData) return;
     68      if (!PerfMemory::is_usable()) return;
     69
...
     90    void PerfMemory::initialize() {
...
    159      OrderAccess::release_store(&_initialized, 1);
    160    }
  ...
    271    bool PerfMemory::is_initialized() {
    272      return OrderAccess::load_acquire(&_initialized) != 0;
    273    }


Can you summarize the role of the is_initialized check versus a direct
UsePerfData check on the VM side i.e. which should be checked when? I
immediately see an issue that Perf_Detach is a no-op when !UsePerfdata,
yet Perf_Attach doesn't examine the flag at all! But perhaps we never
reach those methods because of a higher-level call filtering it out ??
There is split path when attaching to self versus attaching to other
running VM processes. The extra detach processing is only needed
for the remote VM case. If attach fails, there is no need to detach.

Perf.java:
...

  274     private ByteBuffer attachImpl(String user, int lvmid, int mode)
  275             throws IllegalArgumentException, IOException
  276     {
  277         final ByteBuffer b = attach(user, lvmid, mode);
  278
  279         if (lvmid == 0) {
  280             // The native instrumentation buffer for this Java virtual
  281             // machine is never unmapped.
  282             return b;
  283         }
  284         else {
  285             // This is an instrumentation buffer for another Java virtual   286             // machine with native resources that need to be managed. We   287             // create a duplicate of the native ByteBuffer and manage it   288             // with a Cleaner. When the duplicate becomes phantom reachable,
  289             // the native resources will be released.
  290
  291             final ByteBuffer dup = b.duplicate();
  292
  293             CleanerFactory.cleaner()
  294                           .register(dup, new CleanerAction(instance, b));
  295             return dup;
  296         }
  297     }




More below ...


David

On 1/06/2018 5:09 AM, Gary Adams wrote:
>/A patch was done previously to prevent an error when -XX:-UsePerfData />/is used, but this bug was filed to make the setting more visible in the />/jdk.internal.perf package. />//>/   Webrev: http://cr.openjdk.java.net/~gadams/8069149/webrev.00/ <http://cr.openjdk.java.net/%7Egadams/8069149/webrev.00/> />/   Issue: https://bugs.openjdk.java.net/browse/JDK-8069149 />//>/I have tried a few initial tests using: />//>/   make run-tests \ />/ TEST_OPTS=VM_OPTIONS=-XX:-UsePerfData \ />/ TEST=open/test/jdk/sun/management/jmxremote />//>/While I'm tracking down one test timeout,  I'd like to get some />/feedback on the approach used here : />//>/   - the basic mechanism propagates the "is initialized" state up to />/Perf.java; />/      does the state need to be exposed to users getPerf(), or is it />/sufficient/

Within PerfMemory UsePerfData and is_usable() are used for guarding execution. Perhaps that is what needs to be propagated up?

/The is_usable() state is used to ensure the memory is initialized and not yet detached. We don't need that specific condition here. > /Unclear what the control flow is here. I don't see that Perf triggers
initialization of the perf subsystem, so what guarantees initialization should have happened by the time the Perf class is initialized ??

The private constructor and the static initializer in Perf ensures
the singleton behavior at class initialization. The perfMemory_init()
is handled during VM initialization.

init.cpp:

     90    void vm_init_globals() {
     91      check_ThreadShadow();
     92      basic_types_init();
     93      eventlog_init();
     94      mutex_init();
     95      chunkpool_init();
     96      perfMemory_init();
     97      SuspendibleThreadSet_init();
     98    }


>/    - an existing use of IOException was used in the case of attach /> >/failures; /> >/       added IOExceptions to the createXXX methods if the memory was not /> >/initialized / On the VM side can you at least add a message to the IOException so that the user can tell why it was thrown.

Initially, I included a message with the IOException, and noticed the other exceptions being thrown in the surrounding code did not include a message,
so opted for the empty message. There is no ambiguity here.

>/    - presuming a CSR would be needed to cover the IOExceptions /
It's not a public API so no CSR should be needed.

Good to know.

>/    - it appears that jdk.internal.perf has very limited usage, /> >/       is it still needed/used (?) / I can't answer that part, but I don't think UsePerfData is needed any more.

If this turns into "removal of UsePerfData" support, we should move the bug to the runtime group. I grabbed the bug initially, because it looked like
a loose end and a simple thing to address by fulfilling the original
request to propagate the visibility up to the Perf.java code.

Let's see if other reviewers have similar views - that the UsePerfData
flag can be removed. This bug was originally filed against 8u60 and is
currently targetted for 12. I only explored a potential fix at this time
in case we need to get a deprecation process started at this time.

...

Reply via email to