diff --git a/cmd/server/analytics_tie_order_256_test.go b/cmd/server/analytics_tie_order_256_test.go deleted file mode 100644 index 25d44967d..000000000 --- a/cmd/server/analytics_tie_order_256_test.go +++ /dev/null @@ -1,336 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "reflect" - "sort" - "strings" - "testing" - "time" -) - -// Issue #256: analytics lists are built by ranging over a map and were sorted -// on one key only. Go map iteration order is random, so entries with equal -// values came out in a different order on each recompute, and in lists capped -// at N it was random which tied entries made the cut. These tests compute each -// list many times and require the same, fully specified order every time. - -const tieOrderRuns = 50 - -// newTieOrderStore builds an in-memory store over packets with an empty node -// cache, so hops stay unresolved and no DB is needed. -func newTieOrderStore(packets []*StoreTx) *PacketStore { - s := newChannelTestStore(packets) - s.nodeCache = []nodeInfo{} - s.nodePM = buildPrefixMap(nil) - s.nodeCacheTime = time.Now() - return s -} - -// assertSameEveryRun calls compute tieOrderRuns times and fails unless every -// result equals want. -func assertSameEveryRun(t *testing.T, name string, want interface{}, compute func() interface{}) { - t.Helper() - for run := 0; run < tieOrderRuns; run++ { - if got := compute(); !reflect.DeepEqual(got, want) { - t.Errorf("%s, run %d:\n got %v\n want %v", name, run, got, want) - return - } - } -} - -// mapField projects one field of each entry of a []map[string]interface{}. -func mapField(list interface{}, field string) []string { - out := []string{} - for _, e := range list.([]map[string]interface{}) { - out = append(out, fmt.Sprint(e[field])) - } - return out -} - -func makeHashSizeAdvert(pubkey, name, firstSeen string) *StoreTx { - pt := PayloadADVERT - d, _ := json.Marshal(map[string]interface{}{"pubKey": pubkey, "name": name}) - return &StoreTx{ - // Header 0x11: flood route, ADVERT. Path byte 0x41: 2-byte hashes, 1 hop. - RawHex: "1141aabbccdd", - Hash: "adv-" + pubkey + "-" + firstSeen, - FirstSeen: firstSeen, - PayloadType: &pt, - DecodedJSON: string(d), - } -} - -func TestHashSizesMultiByteNodesTieOrder_256(t *testing.T) { - // pubkey → adverts. Six nodes tie on 3, inserted in an order that is not - // the expected one. - counts := []struct { - pk string - n int - }{ - {"f6000000", 3}, {"a1000000", 3}, {"d4000000", 3}, {"0b000000", 5}, - {"c3000000", 3}, {"b2000000", 3}, {"e5000000", 3}, {"99000000", 1}, - } - var packets []*StoreTx - for _, c := range counts { - for i := 0; i < c.n; i++ { - packets = append(packets, makeHashSizeAdvert(c.pk, "N-"+c.pk, fmt.Sprintf("2026-05-01T12:%02d:00Z", i))) - } - } - s := newTieOrderStore(packets) - - want := []string{"0b000000", "a1000000", "b2000000", "c3000000", "d4000000", "e5000000", "f6000000", "99000000"} - assertSameEveryRun(t, "multiByteNodes pubkeys", want, func() interface{} { - return mapField(s.computeAnalyticsHashSizes("", "")["multiByteNodes"], "pubkey") - }) -} - -func TestHashSizesTopHopsTieOrder_256(t *testing.T) { - // 60 distinct 2-byte hops, each seen twice, plus one seen three times. - // The list is capped at 50, so the tie also decides which hops are listed. - var packets []*StoreTx - var hops []string - for i := 0; i < 60; i++ { - hops = append(hops, fmt.Sprintf("%04x", (i*37)%256+0x1000)) - } - add := func(hop string) { - pt := PayloadTXT_MSG - packets = append(packets, &StoreTx{ - RawHex: "0941aabb", Hash: fmt.Sprintf("h%d", len(packets)), - FirstSeen: "2026-05-01T12:00:00Z", PayloadType: &pt, - PathJSON: `["` + hop + `"]`, - }) - } - for _, h := range hops { - add(h) - add(h) - } - add("ffff") - add("ffff") - add("ffff") - s := newTieOrderStore(packets) - - sorted := append([]string(nil), hops...) - sort.Strings(sorted) - want := append([]string{"ffff"}, sorted[:49]...) - assertSameEveryRun(t, "topHops hex", want, func() interface{} { - return mapField(s.computeAnalyticsHashSizes("", "")["topHops"], "hex") - }) -} - -func TestChannelAnalyticsTieOrder_256(t *testing.T) { - var packets []*StoreTx - add := func(chHash int, channel, sender, firstSeen string) { - tx := makeGrpTxWithStatus(chHash, channel, "hi", sender, "decrypted") - tx.ID = len(packets) + 1 - tx.FirstSeen = firstSeen - packets = append(packets, tx) - } - // Six channels with 2 messages each, one with 4. Each message has its own - // sender (20 senders with 1 message, capped at 15), plus one sender with 3. - sender := 0 - nextSender := func() string { sender++; return fmt.Sprintf("s%02d", (sender*7)%23) } - for _, ch := range []int{60, 10, 50, 20, 40, 30} { - for i := 0; i < 2; i++ { - add(ch, fmt.Sprintf("#c%d", ch), nextSender(), "2026-05-01T12:00:00Z") - } - } - for i := 0; i < 4; i++ { - add(70, "#c70", "zz-top", "2026-05-01T13:00:00Z") - } - for i := 0; i < 8; i++ { - add(80, "#c80", nextSender(), "2026-05-01T14:00:00Z") - } - s := newTieOrderStore(packets) - s.byPayloadType[5] = packets - compute := func() map[string]interface{} { return s.computeAnalyticsChannels("", "", TimeWindow{}) } - - // channels: messages desc, then hash asc. - assertSameEveryRun(t, "channels hash", []string{"80", "70", "10", "20", "30", "40", "50", "60"}, func() interface{} { - return mapField(compute()["channels"], "hash") - }) - - // topSenders: count desc, then name asc, capped at 15. - var names []string - for k := range senderSet(packets) { - if k != "zz-top" { - names = append(names, k) - } - } - sort.Strings(names) - wantSenders := append([]string{"zz-top"}, names[:14]...) - assertSameEveryRun(t, "topSenders name", wantSenders, func() interface{} { - return mapField(compute()["topSenders"], "name") - }) - - // channelTimeline: hour asc, then channel asc. - wantTL := []string{ - "2026-05-01T12|#c10", "2026-05-01T12|#c20", "2026-05-01T12|#c30", - "2026-05-01T12|#c40", "2026-05-01T12|#c50", "2026-05-01T12|#c60", - "2026-05-01T13|#c70", "2026-05-01T14|#c80", - } - assertSameEveryRun(t, "channelTimeline hour|channel", wantTL, func() interface{} { - out := []string{} - for _, e := range compute()["channelTimeline"].([]map[string]interface{}) { - out = append(out, e["hour"].(string)+"|"+e["channel"].(string)) - } - return out - }) -} - -func senderSet(packets []*StoreTx) map[string]bool { - out := map[string]bool{} - for _, tx := range packets { - var d struct { - Sender string `json:"sender"` - } - json.Unmarshal([]byte(tx.DecodedJSON), &d) - out[d.Sender] = true - } - return out -} - -func TestRFAnalyticsTieOrder_256(t *testing.T) { - var packets []*StoreTx - snr := 5.0 - // Payload types 0..6 each with 2 packets (one observation each), type 8 - // with 3. - add := func(ptype int) { - pt := ptype - id := len(packets) + 1 - packets = append(packets, &StoreTx{ - ID: id, Hash: fmt.Sprintf("rf%d", id), RawHex: "0000", - FirstSeen: "2026-05-01T12:00:00Z", PayloadType: &pt, - Observations: []*StoreObs{{ID: id, ObserverID: "obs1", SNR: &snr, Timestamp: "2026-05-01T12:00:00Z"}}, - }) - } - for _, p := range []int{6, 2, 4, 0, 5, 1, 3} { - add(p) - add(p) - } - add(8) - add(8) - add(8) - s := newTieOrderStore(packets) - compute := func() map[string]interface{} { return s.computeAnalyticsRF("", "", TimeWindow{}) } - - assertSameEveryRun(t, "payloadTypes type", []int{8, 0, 1, 2, 3, 4, 5, 6}, func() interface{} { - v := reflect.ValueOf(compute()["payloadTypes"]) - out := []int{} - for i := 0; i < v.Len(); i++ { - out = append(out, int(v.Index(i).FieldByName("Type").Int())) - } - return out - }) - - // snrByType: count desc, then type name asc. - var tied []string - for _, p := range []int{0, 1, 2, 3, 4, 5, 6} { - tied = append(tied, payloadTypeNames[p]) - } - sort.Strings(tied) - wantSNR := append([]string{payloadTypeNames[8]}, tied...) - assertSameEveryRun(t, "snrByType name", wantSNR, func() interface{} { - v := reflect.ValueOf(compute()["snrByType"]) - out := []string{} - for i := 0; i < v.Len(); i++ { - out = append(out, v.Index(i).FieldByName("Name").String()) - } - return out - }) -} - -func TestTopologyAnalyticsTieOrder_256(t *testing.T) { - var packets []*StoreTx - add := func(obsID, path string) { - id := len(packets) + 1 - packets = append(packets, &StoreTx{ - ID: id, Hash: fmt.Sprintf("tp%d", id), FirstSeen: "2026-05-01T12:00:00Z", - ObserverID: obsID, ObserverName: "Name-" + obsID, PathJSON: path, - }) - } - // Six observers each hear the same six 2-hop paths once, so every hop, - // every pair, every ring entry and every cross-observer entry ties. - observers := []string{"obs-f", "obs-a", "obs-d", "obs-c", "obs-e", "obs-b"} - paths := []string{`["f1","e1"]`, `["a1","b1"]`, `["d1","c1"]`} - for _, o := range observers { - for _, p := range paths { - add(o, p) - } - } - s := newTieOrderStore(packets) - compute := func() map[string]interface{} { return s.computeAnalyticsTopology("", "", TimeWindow{}) } - - assertSameEveryRun(t, "topRepeaters hop", []string{"a1", "b1", "c1", "d1", "e1", "f1"}, func() interface{} { - return mapField(compute()["topRepeaters"], "hop") - }) - assertSameEveryRun(t, "topPairs hopA|hopB", []string{"a1|b1", "c1|d1", "e1|f1"}, func() interface{} { - out := []string{} - for _, e := range compute()["topPairs"].([]map[string]interface{}) { - out = append(out, e["hopA"].(string)+"|"+e["hopB"].(string)) - } - return out - }) - // observers had no sort at all; the first one is the default tab. - assertSameEveryRun(t, "observers id", []string{"obs-a", "obs-b", "obs-c", "obs-d", "obs-e", "obs-f"}, func() interface{} { - return mapField(compute()["observers"], "id") - }) - assertSameEveryRun(t, "perObserverReach obs-a rings", []string{"1:b1,c1,e1", "2:a1,d1,f1"}, func() interface{} { - reach := compute()["perObserverReach"].(map[string]interface{})["obs-a"].(map[string]interface{}) - out := []string{} - for _, r := range reach["rings"].([]map[string]interface{}) { - out = append(out, fmt.Sprintf("%d:%s", r["hops"], strings.Join(mapField(r["nodes"], "hop"), ","))) - } - return out - }) - wantObs := strings.Join([]string{"obs-a", "obs-b", "obs-c", "obs-d", "obs-e", "obs-f"}, ",") - assertSameEveryRun(t, "multiObsNodes hop:observers", []string{ - "a1:" + wantObs, "b1:" + wantObs, "c1:" + wantObs, "d1:" + wantObs, "e1:" + wantObs, "f1:" + wantObs, - }, func() interface{} { - out := []string{} - for _, e := range compute()["multiObsNodes"].([]map[string]interface{}) { - out = append(out, e["hop"].(string)+":"+strings.Join(mapField(e["observers"], "observer_id"), ",")) - } - return out - }) - // bestPathList: minDist asc, then hop asc; among observers tied on - // minDist the lowest observer id is reported. - assertSameEveryRun(t, "bestPathList hop@observer", []string{ - "b1@obs-a", "c1@obs-a", "e1@obs-a", "a1@obs-a", "d1@obs-a", "f1@obs-a", - }, func() interface{} { - out := []string{} - for _, e := range compute()["bestPathList"].([]map[string]interface{}) { - out = append(out, e["hop"].(string)+"@"+e["observer_id"].(string)) - } - return out - }) -} - -func TestDistanceTopHopsTieOrder_256(t *testing.T) { - // Six pairs at exactly the same distance, one farther, one nearer. - var hops []distHopRecord - for _, p := range [][2]string{{"F", "f"}, {"A", "a"}, {"D", "d"}, {"C", "c"}, {"E", "e"}, {"B", "b"}} { - hops = append(hops, distHopRecord{FromPk: p[0], ToPk: p[1], Dist: 42, Hash: "h" + p[0]}) - } - hops = append(hops, distHopRecord{FromPk: "Z", ToPk: "z", Dist: 99, Hash: "hZ"}) - hops = append(hops, distHopRecord{FromPk: "Y", ToPk: "y", Dist: 1, Hash: "hY"}) - - // limit 5 also checks which tied pairs make the cut. - assertSameEveryRun(t, "distance topHops fromPk", []string{"Z", "A", "B", "C", "D"}, func() interface{} { - return mapField(dedupeHopsByPair(hops, 5), "fromPk") - }) -} - -func TestRankSubpathsTieOrder_256(t *testing.T) { - counts := map[string]*subpathAccum{} - for _, p := range []string{"F → G", "A → B", "D → E", "C → D", "E → F", "B → C"} { - counts[p] = &subpathAccum{count: 4, raw: strings.ToLower(strings.ReplaceAll(p, " → ", ","))} - } - counts["X → Y"] = &subpathAccum{count: 9, raw: "x,y"} - s := newTieOrderStore(nil) - - assertSameEveryRun(t, "subpaths path", []string{"X → Y", "A → B", "B → C", "C → D"}, func() interface{} { - return mapField(s.rankSubpaths(counts, 100, 4)["subpaths"], "path") - }) -} diff --git a/cmd/server/store.go b/cmd/server/store.go index ca7e617f5..2e4a945b8 100644 --- a/cmd/server/store.go +++ b/cmd/server/store.go @@ -670,14 +670,7 @@ func dedupeHopsByPair(hops []distHopRecord, limit int) []map[string]interface{} for k, v := range pairMap { pairs = append(pairs, pairEntry{k, v}) } - // #256: pairMap iteration order is random; break distance ties on the - // pair key so the order (and which tied pairs make the limit) is stable. - sort.Slice(pairs, func(i, j int) bool { - if pairs[i].agg.best.Dist != pairs[j].agg.best.Dist { - return pairs[i].agg.best.Dist > pairs[j].agg.best.Dist - } - return pairs[i].key < pairs[j].key - }) + sort.Slice(pairs, func(i, j int) bool { return pairs[i].agg.best.Dist > pairs[j].agg.best.Dist }) result := make([]map[string]interface{}, 0, min(limit, len(pairs))) for i, pe := range pairs { if i >= limit { @@ -6674,14 +6667,8 @@ func (s *PacketStore) computeAnalyticsChannels(region, area string, window TimeW "lastActivity": c.LastActivity, "encrypted": c.Encrypted, }) } - // #256: channelMap iteration order is random; ties break on the channel - // hash (the map key) so equal counts keep one order across recomputes. sort.Slice(channelList, func(i, j int) bool { - mi, mj := channelList[i]["messages"].(int), channelList[j]["messages"].(int) - if mi != mj { - return mi > mj - } - return channelList[i]["hash"].(string) < channelList[j]["hash"].(string) + return channelList[i]["messages"].(int) > channelList[j]["messages"].(int) }) // Top senders @@ -6693,12 +6680,7 @@ func (s *PacketStore) computeAnalyticsChannels(region, area string, window TimeW for n, c := range senderCounts { senderList = append(senderList, senderEntry{n, c}) } - sort.Slice(senderList, func(i, j int) bool { - if senderList[i].count != senderList[j].count { - return senderList[i].count > senderList[j].count - } - return senderList[i].name < senderList[j].name - }) + sort.Slice(senderList, func(i, j int) bool { return senderList[i].count > senderList[j].count }) topSenders := make([]map[string]interface{}, 0) for i, e := range senderList { if i >= 15 { @@ -6719,12 +6701,7 @@ func (s *PacketStore) computeAnalyticsChannels(region, area string, window TimeW tlList = append(tlList, tlEntry{parts[0], parts[1], count}) } } - sort.Slice(tlList, func(i, j int) bool { - if tlList[i].hour != tlList[j].hour { - return tlList[i].hour < tlList[j].hour - } - return tlList[i].channel < tlList[j].channel - }) + sort.Slice(tlList, func(i, j int) bool { return tlList[i].hour < tlList[j].hour }) channelTimeline := make([]map[string]interface{}, 0, len(tlList)) for _, e := range tlList { channelTimeline = append(channelTimeline, map[string]interface{}{ @@ -7161,13 +7138,7 @@ func (s *PacketStore) computeAnalyticsRF(region, area string, window TimeWindow) } payloadTypes = append(payloadTypes, ptEntry{Type: t, Name: name, Count: c}) } - // #256: built from maps; ties break on the map key for a stable order. - sort.Slice(payloadTypes, func(i, j int) bool { - if payloadTypes[i].Count != payloadTypes[j].Count { - return payloadTypes[i].Count > payloadTypes[j].Count - } - return payloadTypes[i].Type < payloadTypes[j].Type - }) + sort.Slice(payloadTypes, func(i, j int) bool { return payloadTypes[i].Count > payloadTypes[j].Count }) // SNR by type type snrTypeEntry struct { @@ -7189,12 +7160,7 @@ func (s *PacketStore) computeAnalyticsRF(region, area string, window TimeWindow) Min: minF64(d.vals), Max: maxF64(d.vals), }) } - sort.Slice(snrByTypeArr, func(i, j int) bool { - if snrByTypeArr[i].Count != snrByTypeArr[j].Count { - return snrByTypeArr[i].Count > snrByTypeArr[j].Count - } - return snrByTypeArr[i].Name < snrByTypeArr[j].Name - }) + sort.Slice(snrByTypeArr, func(i, j int) bool { return snrByTypeArr[i].Count > snrByTypeArr[j].Count }) // Signal over time type sigTimeEntry struct { @@ -8206,17 +8172,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW for h, c := range hopFreq { freqList = append(freqList, freqEntry{h, c}) } - // #256: hopFreq/pairFreq iteration order is random; ties break on the - // hop (pair) key so the order and the top-N cut are stable. - byCountThenHop := func(list []freqEntry) func(i, j int) bool { - return func(i, j int) bool { - if list[i].count != list[j].count { - return list[i].count > list[j].count - } - return list[i].hop < list[j].hop - } - } - sort.Slice(freqList, byCountThenHop(freqList)) + sort.Slice(freqList, func(i, j int) bool { return freqList[i].count > freqList[j].count }) topRepeaters := make([]map[string]interface{}, 0) for i, e := range freqList { if i >= 20 { @@ -8236,7 +8192,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW for p, c := range pairFreq { pairList = append(pairList, freqEntry{p, c}) } - sort.Slice(pairList, byCountThenHop(pairList)) + sort.Slice(pairList, func(i, j int) bool { return pairList[i].count > pairList[j].count }) topPairs := make([]map[string]interface{}, 0) for i, e := range pairList { if i >= 15 { @@ -8287,11 +8243,6 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW } observers = append(observers, map[string]interface{}{"id": id, "name": n}) } - // #256: built from a map with no sort; the first entry is the frontend's - // default observer tab, so give it a stable order. - sort.Slice(observers, func(i, j int) bool { - return observers[i]["id"].(string) < observers[j]["id"].(string) - }) // Per-observer reachability perObserverReach := map[string]interface{}{} @@ -8323,11 +8274,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW rings := make([]map[string]interface{}, 0) for dist, nodeList := range byDist { sort.Slice(nodeList, func(i, j int) bool { - ci, cj := nodeList[i]["count"].(int), nodeList[j]["count"].(int) - if ci != cj { - return ci > cj - } - return nodeList[i]["hop"].(string) < nodeList[j]["hop"].(string) + return nodeList[i]["count"].(int) > nodeList[j]["count"].(int) }) rings = append(rings, map[string]interface{}{"hops": dist, "nodes": nodeList}) } @@ -8353,10 +8300,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW "observer_id": obsID, "observer_name": obsName, "minDist": data.minDist, "count": data.count, }) - // #256: perObserver iteration order is random; among observers - // tied on minDist report the lowest observer id. - if bp, ok := bestPath[hop]; !ok || data.minDist < bp["minDist"].(int) || - (data.minDist == bp["minDist"].(int) && obsID < bp["observer_id"].(string)) { + if bp, ok := bestPath[hop]; !ok || data.minDist < bp["minDist"].(int) { bestPath[hop] = map[string]interface{}{ "minDist": data.minDist, "observer_id": obsID, "observer_name": obsName, } @@ -8371,11 +8315,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW continue } sort.Slice(obs, func(i, j int) bool { - di, dj := obs[i]["minDist"].(int), obs[j]["minDist"].(int) - if di != dj { - return di < dj - } - return obs[i]["observer_id"].(string) < obs[j]["observer_id"].(string) + return obs[i]["minDist"].(int) < obs[j]["minDist"].(int) }) r := resolveHop(hop) entry := map[string]interface{}{ @@ -8388,12 +8328,8 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW multiObsNodes = append(multiObsNodes, entry) } sort.Slice(multiObsNodes, func(i, j int) bool { - ni := len(multiObsNodes[i]["observers"].([]map[string]interface{})) - nj := len(multiObsNodes[j]["observers"].([]map[string]interface{})) - if ni != nj { - return ni > nj - } - return multiObsNodes[i]["hop"].(string) < multiObsNodes[j]["hop"].(string) + return len(multiObsNodes[i]["observers"].([]map[string]interface{})) > + len(multiObsNodes[j]["observers"].([]map[string]interface{})) }) if len(multiObsNodes) > 50 { multiObsNodes = multiObsNodes[:50] @@ -8415,11 +8351,7 @@ func (s *PacketStore) computeAnalyticsTopology(region, area string, window TimeW bestPathList = append(bestPathList, entry) } sort.Slice(bestPathList, func(i, j int) bool { - di, dj := bestPathList[i]["minDist"].(int), bestPathList[j]["minDist"].(int) - if di != dj { - return di < dj - } - return bestPathList[i]["hop"].(string) < bestPathList[j]["hop"].(string) + return bestPathList[i]["minDist"].(int) < bestPathList[j]["minDist"].(int) }) if len(bestPathList) > 50 { bestPathList = bestPathList[:50] @@ -9122,14 +9054,8 @@ func (s *PacketStore) computeAnalyticsHashSizes(region, area string) map[string] for hex, data := range uniqueHops { hopList = append(hopList, hopEntry{hex, data}) } - // #256: uniqueHops iteration order is random; ties break on the hop hex - // so the order and the top-50 cut are stable. sort.Slice(hopList, func(i, j int) bool { - ci, cj := hopList[i].data["count"].(int), hopList[j].data["count"].(int) - if ci != cj { - return ci > cj - } - return hopList[i].hex < hopList[j].hex + return hopList[i].data["count"].(int) > hopList[j].data["count"].(int) }) topHops := make([]map[string]interface{}, 0) for i, e := range hopList { @@ -9162,14 +9088,8 @@ func (s *PacketStore) computeAnalyticsHashSizes(region, area string) map[string] }) } } - // #256: byNode iteration order is random; ties break on pubkey so the - // adopters table's default ("server order") is the same on every recompute. sort.Slice(multiByteNodes, func(i, j int) bool { - pi, pj := multiByteNodes[i]["packets"].(int), multiByteNodes[j]["packets"].(int) - if pi != pj { - return pi > pj - } - return multiByteNodes[i]["pubkey"].(string) < multiByteNodes[j]["pubkey"].(string) + return multiByteNodes[i]["packets"].(int) > multiByteNodes[j]["packets"].(int) }) // Distribution by repeaters: count unique REPEATER nodes per hash size @@ -11055,14 +10975,7 @@ func (s *PacketStore) rankSubpaths(counts map[string]*subpathAccum, totalPaths, for path, data := range counts { ranked = append(ranked, subpathEntry{path, data.count, data.raw}) } - // #256: counts iteration order is random; ties break on the path so the - // order and the limit cut are stable. - sort.Slice(ranked, func(i, j int) bool { - if ranked[i].count != ranked[j].count { - return ranked[i].count > ranked[j].count - } - return ranked[i].path < ranked[j].path - }) + sort.Slice(ranked, func(i, j int) bool { return ranked[i].count > ranked[j].count }) if len(ranked) > limit { ranked = ranked[:limit] }