Skip to content
Open
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
23 changes: 23 additions & 0 deletions bin/zkSnapshotRecursiveSummaryToolkit.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@echo off
REM Licensed to the Apache Software Foundation (ASF) under one or more
REM contributor license agreements. See the NOTICE file distributed with
REM this work for additional information regarding copyright ownership.
REM The ASF licenses this file to You under the Apache License, Version 2.0
REM (the "License"); you may not use this file except in compliance with
REM the License. You may obtain a copy of the License at
REM
REM http://www.apache.org/licenses/LICENSE-2.0
REM
REM Unless required by applicable law or agreed to in writing, software
REM distributed under the License is distributed on an "AS IS" BASIS,
REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
REM See the License for the specific language governing permissions and
REM limitations under the License.

setlocal
call "%~dp0zkEnv.cmd"

set ZOOMAIN=org.apache.zookeeper.server.SnapshotRecursiveSummary
call %JAVA% -cp "%CLASSPATH%" %ZOOMAIN% %*

endlocal & exit /b %ERRORLEVEL%
29 changes: 29 additions & 0 deletions bin/zkSnapshotRecursiveSummaryToolkit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash

# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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.

ZOOBIN="${BASH_SOURCE-$0}"
ZOOBIN="$(dirname "${ZOOBIN}")"
ZOOBINDIR="$(cd "${ZOOBIN}"; pwd)"

if [ -e "$ZOOBIN/../libexec/zkEnv.sh" ]; then
. "$ZOOBINDIR"/../libexec/zkEnv.sh
else
. "$ZOOBINDIR"/zkEnv.sh
fi

"$JAVA" -cp "$CLASSPATH" $JVMFLAGS \
org.apache.zookeeper.server.SnapshotRecursiveSummary "$@"
61 changes: 48 additions & 13 deletions zookeeper-docs/src/main/resources/markdown/zookeeperTools.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ limitations under the License.
* [zkTxnLogToolkit.sh](#zkTxnLogToolkit)
* [zkSnapShotToolkit.sh](#zkSnapShotToolkit)
* [zkSnapshotComparer.sh](#zkSnapshotComparer)
* [zkSnapshotRecursiveSummaryToolkit.sh](#zkSnapshotRecursiveSummaryToolkit)

* [Testing](#Testing)
* [Jepsen Test](#jepsen-test)
Expand Down Expand Up @@ -252,24 +253,58 @@ Use `-d`, `--debug` to display filtered paths and comparison details. Use
The batch report visits each depth and sorts paths alphabetically within it.
It retains the upstream empty label for the root in comparison lines.

<a name="zkSnapshotRecursiveSummaryToolkit"></a>

### zkSnapshotRecursiveSummaryToolkit.sh

Recursively summarize one snapshot subtree. This is the native backport of
[ZOOKEEPER-4566](https://github.com/apache/zookeeper/commit/05b215994f5e145c2758c4089828b57ba471b329).

```bash
bin/zkSnapshotRecursiveSummaryToolkit.sh snapshot.1 /app 1
```

Usage: `SnapshotRecursiveSummary <snapshot_file> <starting_node> <max_depth>`.
The starting node must be an existing absolute znode path. The maximum depth is
a non-negative integer: 0 prints every non-leaf node, 1 prints the starting
node and its non-leaf children, 2 adds another level, and so on.
**Depth limits output only, not traversal or totals.**

`children` is the number of all descendants, not just immediate children.
`data` is the sum of payload bytes for the node itself and all descendants.
Null data contributes zero bytes. Leaves contribute to their ancestors' totals
but are not printed; selecting a leaf produces no summary entries.

For example, if `/app` has 2 payload bytes, `/app/branch` has 3, and
`/app/branch/leaf` has 5, the summary is:

```text
/app
children: 2
data: 10
-- /app/branch
-- children: 1
-- data: 8
```

#### Snapshot analysis limitations

The comparer runs offline, reads files without modifying them, and supports
uncompressed, `.gz`, and `.snappy` snapshots, including mixed formats. It
validates snapshot checksums and reports unreadable or corrupt input rather
than silently producing a successful analysis. Invalid arguments or file paths
exit with code 2; snapshot read failures exit with code 1. A Windows launcher
with a `.cmd` extension is included. Both launchers use the existing `zkEnv`
configuration.
Both tools run offline, read files without modifying them, and support
uncompressed, `.gz`, and `.snappy` snapshots (including mixed formats in the
comparer). They validate snapshot checksums and report unreadable or corrupt
input rather than silently producing a successful analysis. Invalid arguments,
file paths or summary starting paths exit with code 2; snapshot read failures
exit with code 1. Windows launchers with the same names and a `.cmd` extension
are also included. The launchers use the existing `zkEnv` configuration.

The comparer **includes ephemeral znodes** present in the snapshot; it does not
Both tools **include ephemeral znodes** present in the snapshot; they do not
report session records. This reflects the upstream traversal behavior, despite
the original description claiming that ephemerals were ignored. It does not
compare payload contents, ACLs, versions or other znode metadata.
the original comparer's description claiming that ephemerals were ignored.
Neither tool compares payload contents, ACLs, versions or other znode metadata.
**Equal sizes/counts do not prove identical contents.** Snapshots may be fuzzy:
the tool does not replay transaction logs, reconstruct point-in-time state,
or establish transaction-consistent equality. It loads snapshots into memory,
and recursive traversal visits the full snapshot.
these tools do not replay transaction logs, reconstruct point-in-time state,
or establish transaction-consistent equality. The tools load snapshots into
memory, and recursive traversal still visits the full selected subtree.

<a name="Testing"></a>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.zookeeper.server;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.ZKUtil;
import org.apache.zookeeper.common.PathUtils;
import org.apache.zookeeper.util.ServiceUtils;

/**
* Recursively summarizes snapshot subtree data sizes and descendant counts.
* Only non-leaf nodes are printed, but totals include all descendants and ephemeral nodes.
* The maximum depth limits output, not traversal; zero means unlimited output depth.
*
* <p>Backported from Apache ZooKeeper commit 05b215994f5e145c2758c4089828b57ba471b329
* (ZOOKEEPER-4566).
*/
@InterfaceAudience.Public
public class SnapshotRecursiveSummary {

public static void main(String[] args) {
if (args.length != 3) {
System.err.println(getUsage());
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
return;
}
try {
new SnapshotRecursiveSummary().run(args[0], args[1], Integer.parseInt(args[2]));
} catch (IllegalArgumentException e) {
System.err.println(e.getMessage());
System.err.println(getUsage());
ServiceUtils.requestSystemExit(ExitCode.INVALID_INVOCATION.getValue());
} catch (IOException e) {
System.err.println("Unable to read snapshot: " + e.getMessage());
ServiceUtils.requestSystemExit(ExitCode.UNEXPECTED_ERROR.getValue());
}
}

public void run(String snapshotFileName, String startingNode, int maxDepth) throws IOException {
PathUtils.validatePath(startingNode);
if (maxDepth < 0) {
throw new IllegalArgumentException("max_depth must be a non-negative integer.");
}
String error = ZKUtil.validateFileInput(snapshotFileName);
if (error != null) {
throw new IllegalArgumentException(error);
}
DataTree dataTree = SnapshotComparer.getSnapshot(new File(snapshotFileName));
if (dataTree.getNode(startingNode) == null) {
throw new IllegalArgumentException("Starting node does not exist: " + startingNode);
}
StringBuilder builder = new StringBuilder();
printZnode(dataTree, startingNode, builder, 0, maxDepth);
System.out.println(builder);
}

private long[] printZnode(DataTree dataTree, String name, StringBuilder builder, int level, int maxDepth) {
DataNode node = dataTree.getNode(name);
Set<String> children;
long dataSize;
synchronized (node) {
dataSize = node.data == null ? 0 : node.data.length;
children = new TreeSet<>(node.getChildren());
}
long[] result = {1L, dataSize};
if (children.isEmpty()) {
return result;
}
StringBuilder childBuilder = new StringBuilder();
for (String child : children) {
long[] childResult = printZnode(dataTree, name + (name.equals("/") ? "" : "/") + child,
childBuilder, level + 1, maxDepth);
result[0] += childResult[0];
result[1] += childResult[1];
}
if (maxDepth == 0 || level <= maxDepth) {
String indent = String.join("", Collections.nCopies(level, "--"));
builder.append(indent).append(" ").append(name).append("\n");
builder.append(indent).append(" children: ").append(result[0] - 1).append("\n");
builder.append(indent).append(" data: ").append(result[1]).append("\n");
builder.append(childBuilder);
}
return result;
}

public static String getUsage() {
String newLine = System.lineSeparator();
return String.join(newLine,
"USAGE:",
"",
"SnapshotRecursiveSummary <snapshot_file> <starting_node> <max_depth>",
"",
"snapshot_file: path to the zookeeper snapshot",
"starting_node: the absolute path in the zookeeper tree where traversal should begin",
"max_depth: non-negative output depth. 0 displays every non-leaf node; "
+ "1 displays the starting node and its non-leaf children; 2 adds another level, and so on. "
+ "This ONLY affects the level of details displayed, NOT the calculation.");
}

}
Loading
Loading