Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ dependencies {
implementation libs.asm // ByteCode manipulation
implementation libs.asm.util // ASM utilities
implementation libs.icu4j // Unicode support
implementation libs.jol.core // JVM object layout and graph footprint estimates
implementation libs.jsoup // HTML5 parsing for HTML::Content::Extractor
implementation libs.jing // RELAX NG validation for XML::LibXML
implementation libs.snakeyaml.engine // YAML processing
Expand Down Expand Up @@ -473,6 +474,8 @@ shadowJar {
destinationDirectory = file("$buildDir/../target")
manifest {
attributes 'Main-Class': 'org.perlonjava.app.cli.Main'
attributes 'Premain-Class': 'org.openjdk.jol.vm.InstrumentationSupport'
attributes 'Launcher-Agent': 'org.openjdk.jol.vm.InstrumentationSupport'
}
exclude 'module-info.class'
exclude 'META-INF/MANIFEST.MF'
Expand Down
2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ priorities and future plans.

## Work in progress

- Bundle `Devel::Size` with JVM-based `size` and `total_size` estimates.

- Accept `experimental::for_list` as Perl 5.44's no-op warning category, align bundled warning masks including `experimental::builtin`, and expose B start-op package identity for Sub::Import compatibility (#1208, #1219, #1206).

- Preserve the input line number when a borrowed anonymous filehandle alias leaves a JVM subroutine scope (#1933).
Expand Down
1 change: 1 addition & 0 deletions docs/reference/bundled-modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Recent CPAN compatibility additions include:
| `PadWalker` | Perl facade over Java runtime metadata | `peek_sub`, `closed_over`, and `set_closed_over`; caller-pad APIs remain unsupported |
| `Devel::Caller` | Perl facade over Java call frames | `caller_cv` and caller argument compatibility used by lexical tooling |
| `Devel::LexAlias` | Perl facade over Java lexical cells | Rebinds local and captured scalar, array, and hash lexicals |
| `Devel::Size` | Perl wrapper plus JOL | Estimates shallow JVM object size and reachable object graph size; totals include PerlOnJava runtime objects, and `$warn`/`$dangle` are compatibility variables only |
| `Encode::Locale` | Pure Perl facade over bundled locale support | Provides LWP and XML::Parser with locale encoding aliases; includes `Encode::Alias` |
| `Crypt::Twofish2` | Java XS bridge | BouncyCastle Twofish engine with ECB, CBC, and CFB1 compatibility |
| `Crypt::Blowfish` | Java XS bridge | BouncyCastle Blowfish engine used by `Crypt::CBC` and `Git::Crypt` |
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ icu4j = "78.3"
jsoup = "1.23.1"
jing = "20241231"
junit-jupiter = "6.1.3"
jol = "0.17"
snakeyaml-engine = "3.1.1"
snappy-java = "1.1.10.8"
sqlite-jdbc = "3.53.2.1"
Expand All @@ -33,6 +34,7 @@ jing = { module = "org.relaxng:jing", version.ref = "jing" }
junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junit-jupiter" }
junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter" }
junit-jupiter-params = { module = "org.junit.jupiter:junit-jupiter-params", version.ref = "junit-jupiter" }
jol-core = { module = "org.openjdk.jol:jol-core", version.ref = "jol" }
snakeyaml-engine = { module = "org.snakeyaml:snakeyaml-engine", version.ref = "snakeyaml-engine" }
snappy-java = { module = "org.xerial.snappy:snappy-java", version.ref = "snappy-java" }
sqlite-jdbc = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite-jdbc" }
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/DevelSize.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.perlonjava.runtime.perlmodule;

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;
import org.perlonjava.runtime.runtimetypes.RuntimeArray;
import org.perlonjava.runtime.runtimetypes.RuntimeList;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;

/** JVM object footprint estimates exposed by Devel::Size. */
public class DevelSize extends PerlModuleBase {

static {
// The launcher may not permit dynamic agent attachment. JOL's Unsafe
// layout strategy still works without it; avoid warning on every call.
System.setProperty("jol.skipInstallAttach", "true");
System.setProperty("jol.skipDynamicAttach", "true");
}

public DevelSize() {
super("Devel::Size", false);
}

public static void initialize() {
DevelSize module = new DevelSize();
try {
module.registerMethod("size", null);
module.registerMethod("total_size", null);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
module.defineExport("EXPORT_OK", "size", "total_size");
module.defineDefaultAndAllTags();
}

/** Return the shallow JVM size of the Perl scalar and its inline payload. */
public static RuntimeList size(RuntimeArray args, int ctx) {
if (args == null || args.isEmpty() || args.get(0) == null) {
return new RuntimeScalar(0L).getList();
}
RuntimeScalar scalar = args.get(0);
long bytes = ClassLayout.parseInstance(scalar).instanceSize();
if (!org.perlonjava.runtime.runtimetypes.RuntimeScalarType.isReference(scalar)
&& scalar.value != null) {
bytes += GraphLayout.parseInstance(scalar.value).totalSize();
}
return new RuntimeScalar(bytes).getList();
}

/** Return the JVM footprint of all objects reachable from the scalar. */
public static RuntimeList total_size(RuntimeArray args, int ctx) {
if (args == null || args.isEmpty() || args.get(0) == null) {
return new RuntimeScalar(0L).getList();
}
long bytes = GraphLayout.parseInstance(args.get(0)).totalSize();
return new RuntimeScalar(bytes).getList();
}
}
21 changes: 21 additions & 0 deletions src/main/perl/lib/Devel/Size.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package Devel::Size;

use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our $VERSION = '0.87';
our @EXPORT_OK = qw(size total_size);
our %EXPORT_TAGS = (all => \@EXPORT_OK);

# Retained for source compatibility. JOL can inspect the JVM graph directly,
# so native XS warnings about incomplete CVs and dangling C pointers do not
# apply; these variables do not change JOL's behavior. The estimate includes
# reachable PerlOnJava implementation objects.
our $warn = 1;
our $dangle = 0;

require XSLoader;
XSLoader::load(__PACKAGE__, $VERSION);

1;
34 changes: 34 additions & 0 deletions src/test/resources/module/Devel-Size/t/basic.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use strict;
use warnings;
use Test::More;

use Devel::Size qw(size total_size);

ok(size(1) > 0, 'size returns a positive estimate');
ok(total_size(1) >= size(1), 'total_size includes the root object');
ok(size('a much longer string value') > size('x'),
'size accounts for scalar string payload');

my $small = [1];
my $large = [1 .. 100];
ok(total_size($large) > total_size($small), 'aggregate size grows with contents');

my $small_hash = { value => 'x' };
my $large_hash = { value => 'x' x 1000 };
ok(total_size($large_hash) > total_size($small_hash),
'hash size grows with contained data');

my $shared = [1 .. 20];
my $twice = [$shared, $shared];
my $once = [$shared];
ok(total_size($twice) < 2 * total_size($once), 'shared referent is deduplicated');

my $cycle = [];
push @$cycle, $cycle;
ok(total_size($cycle) > 0, 'cyclic graph terminates');

use Devel::Size qw(:all);
ok(defined &Devel::Size::size && defined &Devel::Size::total_size,
':all exposes both functions');

done_testing();
Loading